### Install .NET Runtime Custom Action
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Provides a C# custom action for WixSharp installers to check, download, and install the .NET Desktop Runtime. It handles the installation process silently and updates the installer status.
```C#
public static class RuntimeActions
{
///
/// Add-in client .NET version
///
private const string DotnetRuntimeVersion = "7";
///
/// Direct download link
///
private const string DotnetRuntimeUrl = $"https://aka.ms/dotnet/{DotnetRuntimeVersion}.0/windowsdesktop-runtime-win-x64.exe";
///
/// Installing the .NET runtime after installing software
///
[CustomAction]
public static ActionResult InstallDotnet(Session session)
{
try
{
var isRuntimeInstalled = CheckDotnetInstallation();
if (isRuntimeInstalled) return ActionResult.Success;
var destinationPath = Path.Combine(Path.GetTempPath(), "windowsdesktop-runtime-win-x64.exe");
UpdateStatus(session, "Downloading .NET runtime");
DownloadRuntime(destinationPath);
UpdateStatus(session, "Installing .NET runtime");
var status = InstallRuntime(destinationPath);
var result = status switch
{
0 => ActionResult.Success,
1602 => ActionResult.UserExit,
1618 => ActionResult.Success,
_ => ActionResult.Failure
};
File.Delete(destinationPath);
return result;
}
catch (Exception exception)
{
session.Log("Error downloading and installing DotNet: " + exception.Message);
return ActionResult.Failure;
}
}
private static int InstallRuntime(string destinationPath)
{
var startInfo = new ProcessStartInfo(destinationPath)
{
Arguments = "/q",
UseShellExecute = false
};
var installProcess = Process.Start(startInfo)!;
installProcess.WaitForExit();
return installProcess.ExitCode;
}
private static void DownloadRuntime(string destinationPath)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
using var httpClient = new HttpClient();
var responseBytes = httpClient.GetByteArrayAsync(DotnetRuntimeUrl).Result;
File.WriteAllBytes(destinationPath, responseBytes);
}
private static bool CheckDotnetInstallation()
{
var startInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = "--list-runtimes",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
try
{
var process = Process.Start(startInfo)!;
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return output.Split('\n')
.Where(line => line.Contains("Microsoft.WindowsDesktop.App"))
.Any(line => line.Contains($"{DotnetRuntimeVersion}."));
}
catch
{
return false;
}
}
private static void UpdateStatus(Session session, string message)
{
var record = new Record(3);
record[2] = message;
session.Message(InstallMessage.ActionStart, record);
}
}
```
--------------------------------
### WixSharp Project Configuration with Custom Action
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Demonstrates how to integrate the custom .NET Runtime installation action into a WixSharp project. This involves initializing the project and adding the custom action to the sequence.
```C#
var project = new Project
{
Name = "Wix Installer",
UI = WUI.WixUI_FeatureTree,
GUID = new Guid("8F2926C8-3C6C-4D12-9E3C-7DF611CD6DDF"),
Actions = new Action[]
{
new ManagedAction(RuntimeActions.InstallDotnet,
Return.check,
When.Before,
Step.InstallFinalize,
Condition.NOT_Installed)
}
};
```
--------------------------------
### Application Entry Point and Client Initialization
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Initializes the ClientDispatcher and establishes a connection to the server when the application starts. This sets up the communication channel for sending messages.
```C#
///
/// Programme entry point
///
public sealed partial class App
{
public static ClientDispatcher ClientDispatcher { get; }
static App()
{
ClientDispatcher = new ClientDispatcher();
ClientDispatcher.ConnectToServer();
}
}
```
--------------------------------
### Start Client Process with Revit Process ID
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Starts a client process and passes the current Revit process ID as an argument. This allows the client to monitor the Revit process for closure.
```C#
private static void RunClient(string clientName)
{
var startInfo = new ProcessStartInfo
{
FileName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!.AppendPath(clientName),
Arguments = Process.GetCurrentProcess().Id.ToString()
};
Process.Start(startInfo);
}
```
--------------------------------
### C# Example: Sending OS Messages for Interprocess Communication
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
This C# code snippet demonstrates how to send messages to a main window handle using `Win32Api.PostMessage`. It's an example of interprocess communication, specifically for interacting with applications like Revit via its Project Browser plugin.
```csharp
public class DataTransmitter : IEventObserver
{
private void PostMessageToMainWindow(int iCmd) =>
this.HandleOnMainThread((Action) (() =>
Win32Api.PostMessage(Application.UIApp.getUIApplication().MainWindowHandle, 273U, new IntPtr(iCmd), IntPtr.Zero)));
public void HandleShortCut(string key, bool ctrlPressed)
{
string lower = key.ToLower();
switch (PrivateImplementationDetails.ComputeStringHash(lower))
{
case 388133425:
if (!(lower == "f2")) break;
this.PostMessageToMainWindow(DataTransmitter.ID_RENAME);
break;
case 1740784714:
if (!(lower == "delete")) break;
this.PostMessageToMainWindow(DataTransmitter.ID_DELETE);
break;
case 3447633555:
if (!(lower == "contextmenu")) break;
this.PostMessageToMainWindow(DataTransmitter.ID_PROJECTBROWSER_CONTEXT_MENU_POP);
break;
case 3859557458:
if (!(lower == "c") || !ctrlPressed) break;
this.PostMessageToMainWindow(DataTransmitter.ID_COPY);
break;
case 4077666505:
if (!(lower == "v") || !ctrlPressed) break;
this.PostMessageToMainWindow(DataTransmitter.ID_PASTE);
break;
case 4228665076:
if (!(lower == "y") || !ctrlPressed) break;
this.PostMessageToMainWindow(DataTransmitter.ID_REDO);
break;
case 4278997933:
if (!(lower == "z") || !ctrlPressed) break;
this.PostMessageToMainWindow(DataTransmitter.ID_UNDO);
break;
}
}
}
```
--------------------------------
### Client Process Startup and Parent Process Monitoring
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Handles the startup of the client process by parsing command-line arguments to get the parent process ID. It then sets up an event handler to shut down the client when the parent process exits.
```C#
protected override void OnStartup(StartupEventArgs args)
{
ParseCommandArguments(args.Args);
}
private void ParseCommandArguments(string[] args)
{
var ownerPid = args[0];
var ownerProcess = Process.GetProcessById(int.Parse(ownerPid));
ownerProcess.EnableRaisingEvents = true;
ownerProcess.Exited += (_, _) => Shutdown();
}
```
--------------------------------
### Request-Response Protocol Flow
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Details the request-response interaction between the client and server, showing example requests and their corresponding server processing and responses.
```APIDOC
Client Request ──────────► Server Processing ──────────► Client Response
│ │ │
├─ GetAllElements ├─ RevitApi.GetAllElements ├─ ElementsDataResponse
├─ SelectElement ├─ RevitApi.SelectElement ├─ SuccessResponse
└─ DeleteElements └─ RevitApi.DeleteElements └─ DeletionCompletedResponse
```
--------------------------------
### Startup Sequence
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Describes the sequence of events from Revit startup to the frontend loading and displaying elements via named pipes.
```plaintext
1. Revit starts → Backend Application.OnStartup()
2. User clicks command → Command.Execute()
3. Command launches Frontend.exe with Revit PID
4. Frontend connects to Named Pipe server
5. Frontend loads and displays elements
```
--------------------------------
### Named Pipe Server Creation (.NET)
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Demonstrates the creation of a Named Pipe server in C# for interprocess communication. This involves setting up a pipe and waiting for client connections.
```csharp
using System.IO.Pipes;
// ...
var pipeServer = new NamedPipeServerStream("MyPipeName");
await pipeServer.WaitForConnectionAsync();
// ... handle communication ...
```
--------------------------------
### BenchmarkDotNet Performance Comparison
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
This table shows performance measurements for sorting and mathematical calculations between .NET 7.0 and .NET Framework 4.8, highlighting significant speed differences and memory allocation.
```plaintext
BenchmarkDotNet v0.13.9, Windows 11 (10.0.22621.1702/22H2/2022Update/SunValley2)
AMD Ryzen 5 2600X, 1 CPU, 12 logical and 6 physical cores
.NET 7.0 : .NET 7.0.9 (7.0.923.32018), X64 RyuJIT AVX2
.NET Framework 4.8 : .NET Framework 4.8.1 (4.8.9139.0), X64 RyuJIT VectorSize=256
| Method | Runtime | Mean | Error | StdDev | Allocated |
|------------:|-------------------:|---------------:|-------------:|-------------:|----------:|
| ListSort | .NET 7.0 | 1,113,161.8 ns | 20,385.15 ns | 21,811.88 ns | 804753 B |
| ListOrderBy | .NET 7.0 | 1,064,851.1 ns | 12,401.25 ns | 11,600.13 ns | 807054 B |
| MinValue | .NET 7.0 | 979.4 ns | 7.40 ns | 6.56 ns | - |
| MaxValue | .NET 7.0 | 970.6 ns | 4.32 ns | 3.60 ns | - |
| ListSort | .NET Framework 4.8 | 2,144,723.5 ns | 40,359.72 ns | 37,752.51 ns | 1101646 B |
| ListOrderBy | .NET Framework 4.8 | 2,192,414.7 ns | 25,938.78 ns | 24,263.15 ns | 1105311 B |
| MinValue | .NET Framework 4.8 | 58,019.0 ns | 460.30 ns | 430.57 ns | 40 B |
| MaxValue | .NET Framework 4.8 | 66,053.4 ns | 610.28 ns | 541.00 ns | 41 B |
```
--------------------------------
### Adding New Operations: RevitApi Methods
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Provides a placeholder for the Revit API implementation required to support new operations like creating elements.
```csharp
public static string CreateElement(string elementType, LocationData location)
{
// Revit API implementation
}
```
--------------------------------
### Mock Data Service
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
C# code snippet defining the `MockDataService` class, which provides mock Revit elements and simulates network responses for testing purposes.
```csharp
public static class MockDataService
{
// Provides 30 realistic mock elements
public static List GetMockElements()
// Simulates network delay and responses
public static async Task SimulateMockResponse(Request request)
}
```
--------------------------------
### Client Initialization and Response Reading (C#)
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Initializes a named pipe client for bidirectional communication and provides a method to asynchronously read a response from the server.
```C#
_client = NamedPipeUtil.CreateClient(PipeDirection.InOut);
///
/// Read a Response from the server.
///
public async Task ReadResponseAsync() => await Response.ReadAsync(_client);
```
--------------------------------
### Named Pipe Client Creation (.NET)
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Illustrates how to create a Named Pipe client in C# to connect to a running Named Pipe server. This allows for sending and receiving data between processes.
```csharp
using System.IO.Pipes;
// ...
var pipeClient = new NamedPipeClientStream("MyPipeName");
await pipeClient.ConnectAsync();
// ... send/receive data ...
```
--------------------------------
### Backend Components (.NET Framework 4.8)
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Directory structure for the .NET Framework 4.8 backend application, including Revit integration, command handling, API wrappers, and server communication logic.
```plaintext
Backend/
├── Application.cs # Revit application entry point
├── Commands/
│ └── Command.cs # Revit command implementation
├── Core/
│ ├── RevitApi.cs # Revit API wrapper methods
│ └── RevitApiResults.cs # Static result storage for async operations
└── Server/
├── ServerDispatcher.cs # Named pipe server and request handling
├── PipeProtocol.cs # Request/response protocol definitions
└── NamedPipeUtil.cs # Pipe creation utilities
```
--------------------------------
### Frontend Components (.NET 7.0)
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Directory structure for the .NET 7.0 frontend application, including UI, ViewModel, data models, client communication logic, and utility converters.
```plaintext
Frontend/
├── App.xaml.cs # Application entry point and mode detection
├── Views/
│ ├── MainView.xaml # Main UI with DataGrid and controls
│ └── MainView.xaml.cs # Code-behind with mode-specific logic
├── ViewModels/
│ └── MainViewModel.cs # MVVM pattern implementation
├── Models/
│ └── RevitElementModel.cs # Element data model with observable properties
├── Client/
│ ├── ClientDispatcher.cs # Named pipe client communication
│ ├── DebugClientDispatcher.cs # Dual-mode communication handler
│ └── PipeProtocol.cs # Request/response protocol definitions
├── Services/
│ └── MockDataService.cs # Debug mode data provider
└── Converters/
└── ValueConverters.cs # UI binding converters
```
--------------------------------
### C# Handling Client Request with Server Response
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Illustrates the server-side logic for handling a client's `UpdateModel` request. It includes a simulated 2-second delay for a heavy task and sends an `UpdateCompletedResponse` back to the client.
```C#
private async Task ListenAndDispatchConnectionsCoreAsync()
{
while (_server.IsConnected)
{
try
{
var request = await Request.ReadAsync(_server);
// ...
if (request.Type == Request.RequestType.UpdateModel)
{
var printRequest = (UpdateModelRequest) request;
await Task.Delay(TimeSpan.FromSeconds(2));
await WriteResponseAsync(new UpdateCompletedResponse(changes: 69, version: "2.1.7"));
}
}
catch (EndOfStreamException)
{
return; //Pipe disconnected
}
}
}
```
--------------------------------
### NamedPipeClientStream API Documentation
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Documentation for the NamedPipeClientStream class in .NET, used for creating and managing named pipe clients to connect to servers. Covers constructor overloads, pipe options, and stream operations.
```APIDOC
NamedPipeClientStream:
Constructor(serverName: string, pipeName: string, direction: PipeDirection, options: PipeOptions)
serverName: The name of the remote computer on which the pipe server is located.
pipeName: The name of the pipe.
direction: One of the PipeDirection values that specifies the direction of the pipe.
options: One of the PipeOptions values that specifies options for the pipe.
Properties:
IsConnected: Gets a value indicating whether the pipe is connected to a server.
Methods:
Connect(): Connects the client to the pipe.
ConnectAsync(CancellationToken cancellationToken): Asynchronously connects the client to the pipe.
Close(): Closes the current NamedPipeClientStream and releases all resources.
PipeOptions:
Asynchronous: Enables asynchronous operations on the pipe.
WriteThrough: Specifies that data written to the pipe should be sent through the network immediately.
CurrentUserOnly: Specifies that the pipe can only be accessed by the current user (available from .NET 7+).
```
--------------------------------
### Adding New Operations: Update Dispatchers
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Updates server and client dispatchers to handle new operations, routing requests and providing mock responses for CreateElement operations.
```csharp
// ServerDispatcher
case RequestType.CreateElement:
await ProcessCreateElementAsync((CreateElementRequest)request);
break;
// DebugClientDispatcher
RequestType.CreateElement => new ElementCreatedResponse("mock-id-123"),
```
--------------------------------
### NamedPipeServerStream API Documentation
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Documentation for the NamedPipeServerStream class in .NET, used for creating and managing named pipe servers. Covers constructor overloads, pipe options, and stream operations for inter-process communication.
```APIDOC
NamedPipeServerStream:
Constructor(pipeName: string, direction: PipeDirection, maxNumberOfServerInstances: int, transportType: PipeTransmissionMode, options: PipeOptions)
pipeName: The name of the pipe.
direction: One of the PipeDirection values that specifies the direction of the pipe.
maxNumberOfServerInstances: The maximum number of server instances that can be created.
transportType: One of the PipeTransmissionMode values that specifies the transmission mode of the pipe.
options: One of the PipeOptions values that specifies options for the pipe.
Properties:
IsConnected: Gets a value indicating whether the pipe is connected to a client.
PipeDirection: Gets the direction of the pipe.
Methods:
WaitForConnection(): Waits for a client to connect to the pipe.
WaitForConnectionAsync(CancellationToken cancellationToken): Asynchronously waits for a client to connect to the pipe.
Disconnect(): Disconnects the pipe from the client.
Close(): Closes the current NamedPipeServerStream and releases all resources.
PipeOptions:
Asynchronous: Enables asynchronous operations on the pipe.
WriteThrough: Specifies that data written to the pipe should be sent through the network immediately.
CurrentUserOnly: Specifies that the pipe can only be accessed by the current user (available from .NET 7+).
```
--------------------------------
### UpdateModelRequest Implementation
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
A concrete implementation of the `Request` class for updating model settings. It serializes an integer for iterations, a boolean for force update, and a length-prefixed string for the model name.
```C#
///
/// Represents a Request from the client. A Request is as follows.
///
/// Field Name Type Size (bytes)
/// --------------------------------------------------
/// ResponseType Integer 4
/// Iterations Integer 4
/// ForceUpdate Boolean 1
/// ModelName String Variable
///
/// Strings are encoded via a character count prefix as a
/// 32-bit integer, followed by an array of characters.
///
///
public class UpdateModelRequest : Request
{
public int Iterations { get; }
public bool ForceUpdate { get; }
public string ModelName { get; }
public override RequestType Type => RequestType.UpdateModel;
public UpdateModelRequest(string modelName, int iterations, bool forceUpdate)
{
Iterations = iterations;
ForceUpdate = forceUpdate;
ModelName = modelName;
}
protected override void AddRequestBody(BinaryWriter writer)
{
writer.Write(Iterations);
writer.Write(ForceUpdate);
WriteLengthPrefixedString(writer, ModelName);
}
}
```
--------------------------------
### Interprocess Communication Pattern
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Illustrates the interprocess communication flow between the Frontend (.NET 7.0 WPF Client) and the Backend (.NET 4.8 Revit Plugin) using Named Pipes.
```APIDOC
┌─────────────────┐ Named Pipes ┌─────────────────┐
│ Frontend │ ◄──────────────► │ Backend │
│ (.NET 7.0) │ │ (.NET 4.8) │
│ WPF Client │ │ Revit Plugin │
└─────────────────┘ └─────────────────┘
```
--------------------------------
### Static Result Storage for Revit API
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Demonstrates a pattern for storing results from Revit API calls statically to work around threading constraints and interface requirements. This allows asynchronous operations to return data effectively.
```csharp
public static ICollection GetAllElementsWrapper()
{
RevitApiResults.LastElementsResult = GetAllElements();
return new List(); // Satisfy interface
}
// Consumer retrieves from static storage
var elements = RevitApiResults.LastElementsResult ?? new List();
```
--------------------------------
### MainViewModel for Sending Messages
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Provides the business logic for the WPF view, allowing users to send messages to the server via the ClientDispatcher. It uses MVVM patterns with ObservableObject and RelayCommand.
```C#
///
/// WPF view business logic
///
public partial class MainViewModel : ObservableObject
{
[ObservableProperty] private string _message = string.Empty;
[RelayCommand]
private async Task SendMessageAsync()
{
var request = new PrintMessageRequest(Message);
await App.ClientDispatcher.WriteRequestAsync(request);
}
[RelayCommand]
private async Task UpdateModelAsync()
{
```
--------------------------------
### Adding New Operations: Protocol Classes
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Implements protocol classes for new operations, defining the structure of request and response messages for creating and modifying elements.
```csharp
public class CreateElementRequest : Request
{
public string ElementType { get; }
public LocationData Location { get; }
// Implementation...
}
public class ElementCreatedResponse : Response
{
public string NewElementId { get; }
// Implementation...
}
```
--------------------------------
### Revit API Threading Constraints
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Explains the threading constraints of the Revit API, emphasizing the need for `AsyncEventHandler` to marshal calls to the main UI thread and the use of `RevitApiResults` for bridging async boundaries.
```plaintext
- Revit API can only be accessed from the main UI thread
- `AsyncEventHandler` is used to marshal calls to the correct thread
- Static result storage (`RevitApiResults`) bridges async boundaries
```
--------------------------------
### Mode Detection Logic
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
C# code snippet demonstrating how the application detects whether to run in debug mode based on command-line arguments.
```csharp
// Automatic mode detection in App.xaml.cs
var args = Environment.GetCommandLineArgs();
IsDebugMode = args.Length <= 1 || args.Contains("--debug");
if (IsDebugMode)
{
DebugClientDispatcher = new DebugClientDispatcher(true);
}
else
{
ClientDispatcher = new ClientDispatcher();
}
```
--------------------------------
### ViewModel Update Logic with Response Handling (C#)
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Handles updating the ViewModel by sending a request to the server and processing the response. Displays messages based on whether the update was completed or rejected.
```C#
[RelayCommand]
private async Task UpdateModelAsync()
{
var request = new UpdateModelRequest(AppDomain.CurrentDomain.FriendlyName, 666, true);
await App.ClientDispatcher.WriteRequestAsync(request);
var response = await App.ClientDispatcher.ReadResponseAsync();
if (response.Type == Response.ResponseType.UpdateCompleted)
{
var completedResponse = (UpdateCompletedResponse) response;
MessageBox.Show($"{completedResponse.Changes} elements successfully updated to version {completedResponse.Version}");
}
else if (response.Type == Response.ResponseType.Rejected)
{
MessageBox.Show("Update failed");
}
}
```
--------------------------------
### PrintMessageRequest Implementation
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
A concrete implementation of the `Request` class for sending a message. It serializes a string message with a length prefix, intended for simple text-based communication.
```C#
///
/// Represents a Request from the client. A Request is as follows.
///
/// Field Name Type Size (bytes)
/// --------------------------------------------------
/// RequestType Integer 4
/// Message String Variable
///
/// Strings are encoded via a character count prefix as a
/// 32-bit integer, followed by an array of characters.
///
///
public class PrintMessageRequest : Request
{
public string Message { get; }
public override RequestType Type => RequestType.PrintMessage;
public PrintMessageRequest(string message)
{
Message = message;
}
protected override void AddRequestBody(BinaryWriter writer)
{
WriteLengthPrefixedString(writer, Message);
}
}
```
--------------------------------
### Realistic Mock Data
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Details the characteristics of the mock data provided by `MockDataService`, including the number of elements, categories, distribution, and naming conventions.
```plaintext
- 30 diverse elements representing typical Revit project content
- Multiple categories: Walls, Doors, Windows, Structural, MEP, Furniture
- Multi-level distribution: Elements on Level 1 and Level 2
- Realistic naming: Professional element names and types
```
--------------------------------
### MVVM Architecture Pattern
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Describes the Model-View-ViewModel (MVVM) pattern used in the application, highlighting data binding, commands, and service interactions.
```APIDOC
┌─────────────┐ Data Binding ┌─────────────┐ Commands ┌─────────────┐
│ View │ ◄──────────────── │ ViewModel │ ──────────────► │ Service │
│ (MainView) │ │(MainViewModel) │(ClientDispatcher)
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ │ │
UI Events ObservableCollection API Calls
```
--------------------------------
### Production Mode Data Flow
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Outlines the sequence of operations and communication channels involved in the production mode data flow, from user input to UI updates via Revit.
```APIDOC
User Input → ViewModel Command → ClientDispatcher → Named Pipe →
ServerDispatcher → AsyncEventHandler → RevitApi → Revit Document →
Response → Named Pipe → ClientDispatcher → ViewModel → UI Update
```
--------------------------------
### Simulated Operations
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Describes the features of simulated operations in debug mode, including network delay simulation, success/failure scenarios, loading state testing, and error condition simulation.
```plaintext
- Network delay simulation: 500-1500ms delays for realistic testing
- Success/failure scenarios: Configurable response outcomes
- Loading state testing: Progress indicators and status messages
- Error condition simulation: For testing error handling
```
--------------------------------
### Element Loading Flow
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Details the communication flow for loading elements, involving frontend requests, backend event handling, Revit API calls, and data responses.
```plaintext
Frontend Backend Revit API
│ │ │
├─ GetAllElementsRequest ─►│ │
│ ├─ AsyncEventHandler.Raise ─►│
│ │ ├─ FilteredElementCollector
│ │ ├─ Process elements
│ │ └─ Return element data
│ ├─ ElementsDataResponse ◄─│
├─ Update UI ◄─│ │
└─ Display elements │ │
```
--------------------------------
### Base Request Class for Serialization
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
An abstract base class for creating serializable requests. It handles the common logic for writing request type and length-prefixed data to a stream, with derived classes implementing specific request body serialization.
```C#
public abstract class Request
{
public abstract RequestType Type { get; }
protected abstract void AddRequestBody(BinaryWriter writer);
///
/// Write a Request to the given stream.
///
public async Task WriteAsync(Stream outStream)
{
using var memoryStream = new MemoryStream();
using var writer = new BinaryWriter(memoryStream, Encoding.Unicode);
writer.Write((int) Type);
AddRequestBody(writer);
writer.Flush();
// Write the length of the request
var length = checked((int) memoryStream.Length);
// There is no way to know the number of bytes written to
// the pipe stream. We just have to assume all of them are written
await outStream.WriteAsync(BitConverter.GetBytes(length), 0, 4);
memoryStream.Position = 0;
await memoryStream.CopyToAsync(outStream, length);
}
///
/// Write a string to the Writer where the string is encoded
/// as a length prefix (signed 32-bit integer) follows by
/// a sequence of characters.
///
protected static void WriteLengthPrefixedString(BinaryWriter writer, string value)
{
writer.Write(value.Length);
writer.Write(value.ToCharArray());
}
}
```
--------------------------------
### Two-Way Communication with Named Pipes
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Explains how to implement bidirectional communication using Named Pipes, allowing both the server and client to send and receive messages independently.
```apidoc
Two-Way Communication:
- Server-to-Client: Server writes data to its stream, client reads from its stream.
- Client-to-Server: Client writes data to its stream, server reads from its stream.
- Asynchronous Operations: Utilize asynchronous methods (e.g., ReadAsync, WriteAsync) for non-blocking I/O.
- Example Scenario:
1. Client sends a request.
2. Server processes the request and sends a response.
3. Client receives the response.
```
--------------------------------
### Core Response Classes
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Abstract base class for responses and specific implementations for different response types, including ElementsDataResponse, DeletionCompletedResponse, and RejectedResponse.
```csharp
// Base response class
public abstract class Response
{
public abstract ResponseType Type { get; }
protected abstract void AddResponseBody(BinaryWriter writer);
}
// Specific implementations
public class ElementsDataResponse : Response; // List
public class DeletionCompletedResponse : Response; // int Changes
public class RejectedResponse : Response; // string Reason
```
--------------------------------
### ViewModel Command to Delete Elements
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Implements a ViewModel command that initiates the deletion of elements. It sends a 'DeleteElementsRequest' to the client and handles the response, showing a message to the user.
```C#
[RelayCommand]
private async Task DeleteElementsAsync()
{
var request = new DeleteElementsRequest();
await App.ClientDispatcher.WriteRequestAsync(request);
var response = await App.ClientDispatcher.ReadResponseAsync();
if (response.Type == Response.ResponseType.Success)
{
var completedResponse = (DeletionCompletedResponse) response;
MessageBox.Show($"{completedResponse.Changes} elements successfully deleted");
}
else if (response.Type == Response.ResponseType.Rejected)
{
var rejectedResponse = (RejectedResponse) response;
MessageBox.Show($"Deletion failed\n{rejectedResponse.Reason}");
}
}
```
--------------------------------
### Core Request Classes
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Abstract base class for requests and specific implementations for different request types, including GetAllElementsRequest, SelectElementRequest, and DeleteElementsRequest.
```csharp
// Base request class
public abstract class Request
{
public abstract RequestType Type { get; }
protected abstract void AddRequestBody(BinaryWriter writer);
}
// Specific implementations
public class GetAllElementsRequest : Request; // No parameters
public class SelectElementRequest : Request; // ElementId parameter
public class DeleteElementsRequest : Request; // No parameters
```
--------------------------------
### ClientDispatcher for Named Pipe Communication
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Manages client-side connections, timeouts, and request scheduling for named pipe communication. It handles establishing a connection to the server and writing requests asynchronously.
```C#
///
/// This class manages the connections, timeout and general scheduling of requests to the server.
///
public class ClientDispatcher
{
private const int TimeOutNewProcess = 10000;
private Task _connectionTask;
private readonly NamedPipeClientStream _client = NamedPipeUtil.CreateClient(PipeDirection.Out);
///
/// Connects to server without awaiting
///
public void ConnectToServer()
{
_connectionTask = _client.ConnectAsync(TimeOutNewProcess);
}
///
/// Write a Request to the server.
///
public async Task WriteRequestAsync(Request request)
{
await _connectionTask;
await request.WriteAsync(_client);
}
}
```
--------------------------------
### Debug Mode Data Flow
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Details the data flow architecture for the debug mode, highlighting the use of mock services and simulated delays for faster development and testing.
```APIDOC
User Input → ViewModel Command → DebugClientDispatcher →
MockDataService → Simulated Delay → Mock Response →
DebugClientDispatcher → ViewModel → UI Update
```
--------------------------------
### C# Client-Side Response Handling Structure
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Provides the structure for the `Response` class on the client-side, mirroring the server-side definition. This enables the client to correctly interpret and process responses received from the server.
```C#
///
/// Base class for all possible responses to a request.
/// The ResponseType enum should list all possible response types
/// and ReadResponse creates the appropriate response subclass based
/// on the response type sent by the client.
/// The format of a response is:
///
/// Field Name Field Type Size (bytes)
/// -------------------------------------------------
/// ResponseType enum ResponseType 4
/// ResponseBody Response subclass variable
///
///
public abstract class Response
{
public enum ResponseType
{
// The update request completed on the server and the results are contained in the message.
UpdateCompleted,
// The request was rejected by the server.
Rejected
}
public abstract ResponseType Type { get; }
///
```
--------------------------------
### Read Length-Prefixed String (C#)
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Reads a string from a BinaryReader, where the string is prefixed with its length as a signed 32-bit integer.
```C#
///
/// Read a string from the Reader where the string is encoded
/// as a length prefix (signed 32-bit integer) followed by
/// a sequence of characters.
///
protected static string ReadLengthPrefixedString(BinaryReader reader)
{
// Same as request class from server
}
```
--------------------------------
### Named Pipe Connection Management
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Covers the essential aspects of managing Named Pipe connections, including establishing, maintaining, and closing connections gracefully. Proper management prevents resource leaks.
```apidoc
Connection Management:
- Server: Listen for connections, accept one at a time or concurrently.
- Client: Attempt to connect, handle connection failures.
- Keep-Alive: Implement a mechanism to detect and handle broken pipes.
- Closing: Ensure pipes are properly closed and disposed of when no longer needed to release system resources.
```
--------------------------------
### Create NamedPipeClientStream in C#
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Provides a C# method to create a NamedPipeClientStream for connecting to a Named Pipe server. It uses a generated pipe name and supports asynchronous operations and user-specific access. Note that PipeOptions.CurrentUserOnly is available from .NET 7 onwards.
```C#
///
/// Create a client for the current user only
///
public static NamedPipeClientStream CreateClient(PipeDirection? pipeDirection = null)
{
const PipeOptions pipeOptions = PipeOptions.Asynchronous | PipeOptions.WriteThrough | PipeOptions.CurrentUserOnly;
return new NamedPipeClientStream(".",
GetPipeName(),
pipeDirection ?? PipeDirection.Out,
pipeOptions);
}
private static string GetPipeName()
{
var clientDirectory = AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar);
var pipeNameInput = $"{System.Environment.UserName}.{clientDirectory}";
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(pipeNameInput));
return Convert.ToBase64String(bytes)
.Replace("/"."_")
.Replace("="."");
}
```
--------------------------------
### Frontend Async Operations
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Describes how asynchronous operations are handled in the frontend to maintain a non-blocking UI, including the use of loading indicators and disabling commands during operations.
```plaintext
- All UI operations are non-blocking
- Loading indicators provide user feedback
- Commands are disabled during operations to prevent conflicts
```
--------------------------------
### Element Selection Flow
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Outlines the communication flow for selecting an element, including frontend requests, backend processing, Revit API interactions, and UI updates.
```plaintext
Frontend Backend Revit API
│ │ │
├─ SelectElementRequest(ID)►│ │
│ ├─ AsyncEventHandler.Raise ─►│
│ │ ├─ Document.GetElement(ID)
│ │ ├─ Selection.SetElementIds()
│ │ ├─ UiDocument.ShowElements()
│ │ └─ Return success/failure
│ ├─ SuccessResponse ◄─│
├─ Update selection UI ◄─│ │
└─ Show confirmation │ │
```
--------------------------------
### C# Server Dispatcher Modification for Responses
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
Demonstrates how to modify the `ServerDispatcher` to send responses back to the client. This includes changing the pipe direction to bidirectional and implementing a `WriteResponseAsync` method.
```C#
_server = NamedPipeUtil.CreateServer(PipeDirection.InOut);
///
/// Write a Response to the client.
///
public async Task WriteResponseAsync(Response response) => await response.WriteAsync(_server);
```
--------------------------------
### Class Relationship Diagram
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Visual representation of the class relationships within the application, illustrating how components like App, MainViewModel, ClientDispatcher, and the UI (MainView) interact.
```APIDOC
┌─────────────────┐ creates ┌─────────────────┐
│ App │ ────────────► │ MainViewModel │
└─────────────────┘ └─────────────────┘
│ │
│ │ uses
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ClientDispatcher │ │ ObservableCollection
│ OR │ ││
│DebugClient- │ └─────────────────┘
│ Dispatcher │ │
└─────────────────┘ │ binds to
│ ▼
│ communicates ┌─────────────────┐
▼ │ MainView │
┌─────────────────┐ │ (DataGrid) │
│ MockDataService│ └─────────────────┘
│ OR │
│ Named Pipe │
│ Server │
└─────────────────┘
```
--------------------------------
### Performance-Optimized Element Collection
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/architecture.md
Collects elements from a Revit document with performance optimizations, filtering out element types and specific categories, and limiting the results to the first 1000 elements.
```csharp
var collector = new FilteredElementCollector(Document)
.WhereElementIsNotElementType()
.Where(e => e.Category != null && !e.Category.Name.Contains("Constraints"));
return elements.Take(1000).ToList(); // Performance limit
```
--------------------------------
### C# Request Handling and Deserialization
Source: https://github.com/trgiangv/interprocesscommunication/blob/main/Readme.md
This C# code defines a base `Request` class with an enum for `RequestType` and a static `ReadAsync` method to deserialize requests from a stream. It supports `PrintMessageRequest` and `UpdateModelRequest` by reading length-prefixed strings and other data types. Helper methods like `ReadAllAsync` and `ReadLengthPrefixedString` are included for stream operations.
```csharp
///
/// Represents a request from the client. A request is as follows.
///
/// Field Name Type Size (bytes)
/// ----------------------------------------------------
/// RequestType enum RequestType 4
/// RequestBody Request subclass variable
///
///
public abstract class Request
{
public enum RequestType
{
PrintMessage,
UpdateModel
}
public abstract RequestType Type { get; }
///
/// Read a Request from the given stream.
///
public static async Task ReadAsync(Stream stream)
{
var lengthBuffer = new byte[4];
await ReadAllAsync(stream, lengthBuffer, 4).ConfigureAwait(false);
var length = BitConverter.ToUInt32(lengthBuffer, 0);
var requestBuffer = new byte[length];
await ReadAllAsync(stream, requestBuffer, requestBuffer.Length);
using var reader = new BinaryReader(new MemoryStream(requestBuffer), Encoding.Unicode);
var requestType = (RequestType) reader.ReadInt32();
return requestType switch
{
RequestType.PrintMessage => PrintMessageRequest.Create(reader),
RequestType.UpdateModel => UpdateModelRequest.Create(reader),
_ => throw new ArgumentOutOfRangeException()
};
}
///
/// This task does not complete until we are completely done reading.
///
private static async Task ReadAllAsync(Stream stream, byte[] buffer, int count)
{
var totalBytesRead = 0;
do
{
var bytesRead = await stream.ReadAsync(buffer, totalBytesRead, count - totalBytesRead);
if (bytesRead == 0) throw new EndOfStreamException("Reached end of stream before end of read.");
totalBytesRead += bytesRead;
} while (totalBytesRead < count);
}
///
/// Read a string from the Reader where the string is encoded
/// as a length prefix (signed 32-bit integer) followed by
/// a sequence of characters.
///
protected static string ReadLengthPrefixedString(BinaryReader reader)
{
var length = reader.ReadInt32();
return length < 0 ? null : new string(reader.ReadChars(length));
}
}
///
/// Represents a Request from the client. A Request is as follows.
///
/// Field Name Type Size (bytes)
/// --------------------------------------------------
/// RequestType Integer 4
/// Message String Variable
///
/// Strings are encoded via a character count prefix as a
/// 32-bit integer, followed by an array of characters.
///
///
public class PrintMessageRequest : Request
{
public string Message { get; }
public override RequestType Type => RequestType.PrintMessage;
public PrintMessageRequest(string message)
{
Message = message;
}
protected override void AddRequestBody(BinaryWriter writer)
{
WriteLengthPrefixedString(writer, Message);
}
}
///
/// Represents a Request from the client. A Request is as follows.
///
/// Field Name Type Size (bytes)
/// --------------------------------------------------
/// RequestType Integer 4
/// Iterations Integer 4
/// ForceUpdate Boolean 1
/// ModelName String Variable
///
/// Strings are encoded via a character count prefix as a
/// 32-bit integer, followed by an array of characters.
///
///
public class UpdateModelRequest : Request
{
public int Iterations { get; }
public bool ForceUpdate { get; }
public string ModelName { get; }
public override RequestType Type => RequestType.UpdateModel;
public UpdateModelRequest(string modelName, int iterations, bool forceUpdate)
{
Iterations = iterations;
ForceUpdate = forceUpdate;
ModelName = modelName;
}
protected override void AddRequestBody(BinaryWriter writer)
{
writer.Write(Iterations);
writer.Write(ForceUpdate);
WriteLengthPrefixedString(writer, ModelName);
}
}
```