### 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); } } ```