# WebSocket Implementation Code Templates This file contains ready-to-use code templates for implementing WebSocket support in WireMock.Net.Minimal. --- ## 1. Abstraction Layer (WireMock.Net.Abstractions) ### File: `Admin/Mappings/WebSocketModel.cs` ```csharp // Copyright © WireMock.Net namespace WireMock.Admin.Mappings; /// /// WebSocket message model for admin API serialization /// public class WebSocketMessageModel { /// /// Delay before sending this message (in milliseconds) /// public int? DelayMs { get; set; } /// /// Text message payload /// public string? BodyAsString { get; set; } /// /// Binary message payload (base64 encoded when serialized) /// public byte[]? BodyAsBytes { get; set; } /// /// Indicates if message is text (true) or binary (false) /// public bool IsText { get; set; } = true; /// /// Message index (for ordering) /// public int Index { get; set; } } /// /// WebSocket response configuration model /// public class WebSocketResponseModel { /// /// Messages to send after WebSocket connection is established /// public List Messages { get; set; } = new(); /// /// Whether to apply transformers to message content /// public bool UseTransformer { get; set; } /// /// Type of transformer to use (Handlebars, Scriban, etc.) /// public string? TransformerType { get; set; } /// /// Close code when connection is terminated (default 1000 = normal closure) /// public int? CloseCode { get; set; } /// /// Close reason/message /// public string? CloseMessage { get; set; } /// /// Indicates if a callback is used for dynamic message generation /// public bool HasCallback { get; set; } /// /// Subprotocol if negotiated /// public string? Subprotocol { get; set; } } ``` ### File: `BuilderExtensions/WebSocketResponseModelBuilder.cs` ```csharp // Copyright © WireMock.Net // ReSharper disable once CheckNamespace namespace WireMock.Admin.Mappings; /// /// Fluent builder for WebSocket response models /// public partial class WebSocketResponseModelBuilder { public WebSocketResponseModelBuilder WithMessage(string body, int? delayMs = null) { Messages.Add(new WebSocketMessageModel { BodyAsString = body, DelayMs = delayMs, IsText = true, Index = Messages.Count }); return this; } public WebSocketResponseModelBuilder WithBinaryMessage(byte[] data, int? delayMs = null) { Messages.Add(new WebSocketMessageModel { BodyAsBytes = data, DelayMs = delayMs, IsText = false, Index = Messages.Count }); return this; } public WebSocketResponseModelBuilder WithClose(int? closeCode = 1000, string? reason = null) { CloseCode = closeCode; CloseMessage = reason; return this; } public WebSocketResponseModelBuilder WithTransformer(string transformerType) { UseTransformer = true; TransformerType = transformerType; return this; } public List Messages { get; } = new(); public bool UseTransformer { get; set; } public string? TransformerType { get; set; } public int? CloseCode { get; set; } public string? CloseMessage { get; set; } public WebSocketResponseModel Build() { return new WebSocketResponseModel { Messages = Messages, UseTransformer = UseTransformer, TransformerType = TransformerType, CloseCode = CloseCode, CloseMessage = CloseMessage }; } } ``` --- ## 2. Domain Models (WireMock.Net.Minimal) ### File: `Models/WebSocketMessage.cs` ```csharp // Copyright © WireMock.Net namespace WireMock.Models; /// /// Represents a single WebSocket message to be sent /// public class WebSocketMessage : IWebSocketMessage { /// /// Delay before sending this message (milliseconds) /// public int DelayMs { get; set; } /// /// Text message payload /// public string? BodyAsString { get; set; } /// /// Binary message payload /// public byte[]? BodyAsBytes { get; set; } /// /// True if text message, false if binary /// public bool IsText { get; set; } = true; /// /// Optional message ID for tracking /// public string? Id { get; set; } /// /// Optional correlation ID from request /// public string? CorrelationId { get; set; } } ``` ### File: `Models/WebSocketResponse.cs` ```csharp // Copyright © WireMock.Net using WireMock.Transformers; using WireMock.Types; namespace WireMock.Models; /// /// Defines the WebSocket response configuration /// public class WebSocketResponse : IWebSocketResponse { /// /// Messages to send after connection established /// public List Messages { get; set; } = new(); /// /// Whether to apply transformers to messages /// public bool UseTransformer { get; set; } /// /// Type of transformer (Handlebars or Scriban) /// public TransformerType TransformerType { get; set; } = TransformerType.Handlebars; /// /// WebSocket close code /// public int? CloseCode { get; set; } = 1000; /// /// Close reason /// public string? CloseMessage { get; set; } /// /// Optional subprotocol negotiation /// public string? Subprotocol { get; set; } /// /// Time to wait before closing (if empty close message list, milliseconds) /// public int? AutoCloseDelayMs { get; set; } } ``` --- ## 3. Request Builder Extension (WireMock.Net.Minimal) ### File: `RequestBuilders/Request.WithWebSocket.cs` ```csharp // Copyright © WireMock.Net using Stef.Validation; using WireMock.Matchers; using WireMock.Matchers.Request; namespace WireMock.RequestBuilders; /// /// WebSocket-specific request matching extensions /// public partial class Request { /// /// Match WebSocket upgrade requests (Connection: Upgrade, Upgrade: websocket headers) /// /// The request builder for chaining public IRequestBuilder WithWebSocketUpgrade() { Add(new RequestMessageHeaderMatcher("Upgrade", "websocket", matchBehaviour: MatchBehaviour.AcceptOnMatch)); Add(new RequestMessageHeaderMatcher("Connection", new WildcardMatcher("*Upgrade*"))); return this; } /// /// Match WebSocket connection by path and automatically add upgrade headers /// /// WebSocket path (e.g., "/ws", "/api/chat") /// The request builder for chaining public IRequestBuilder WithWebSocketPath(string path) { Guard.NotNullOrWhiteSpace(path); return WithPath(path).WithWebSocketUpgrade(); } /// /// Match specific WebSocket subprotocol in Sec-WebSocket-Protocol header /// /// Subprotocol name (e.g., "chat", "superchat") /// The request builder for chaining public IRequestBuilder WithWebSocketSubprotocol(string subprotocol) { Guard.NotNullOrWhiteSpace(subprotocol); Add(new RequestMessageHeaderMatcher("Sec-WebSocket-Protocol", subprotocol)); return this; } /// /// Match WebSocket with specific version (typically 13) /// /// WebSocket version number /// The request builder for chaining public IRequestBuilder WithWebSocketVersion(string version = "13") { Guard.NotNullOrWhiteSpace(version); Add(new RequestMessageHeaderMatcher("Sec-WebSocket-Version", version)); return this; } /// /// Match WebSocket with client origin (CORS) /// /// Origin URL /// The request builder for chaining public IRequestBuilder WithWebSocketOrigin(string origin) { Guard.NotNullOrWhiteSpace(origin); Add(new RequestMessageHeaderMatcher("Origin", origin)); return this; } } ``` --- ## 4. Response Builder Extension (WireMock.Net.Minimal) ### File: `ResponseBuilders/Response.WithWebSocket.cs` ```csharp // Copyright © WireMock.Net using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Stef.Validation; using WireMock.Models; using WireMock.Transformers; using WireMock.Types; namespace WireMock.ResponseBuilders; /// /// WebSocket response builder extensions /// public partial class Response { /// /// WebSocket response configuration /// public IWebSocketResponse? WebSocketResponse { get; private set; } /// /// Indicates if WithWebSocket was used /// public bool WithWebSocketUsed { get; private set; } /// /// Callback for dynamic WebSocket message generation /// [MemberNotNullWhen(true, nameof(WithWebSocketCallbackUsed))] public Func>>? WebSocketCallbackAsync { get; private set; } /// /// Indicates if WebSocket callback is used /// public bool WithWebSocketCallbackUsed => WebSocketCallbackAsync != null; /// /// Subprotocol to negotiate in WebSocket handshake /// public string? WebSocketSubprotocol { get; private set; } /// /// Configure WebSocket response with fluent builder /// /// Configuration action /// Response builder for chaining public IResponseBuilder WithWebSocket(Action configure) { Guard.NotNull(configure); var builder = new WebSocketResponseBuilder(); configure(builder); WithWebSocketUsed = true; WebSocketResponse = builder.Build(); return this; } /// /// Configure WebSocket response with a single message /// /// Message text /// Optional delay before sending (milliseconds) /// Response builder for chaining public IResponseBuilder WithWebSocketMessage(string message, int? delayMs = null) { Guard.NotNullOrWhiteSpace(message); return WithWebSocket(b => b.WithMessage(message, delayMs)); } /// /// Configure WebSocket response with JSON message /// /// Object to serialize as JSON /// Optional delay before sending (milliseconds) /// Response builder for chaining public IResponseBuilder WithWebSocketJsonMessage(object data, int? delayMs = null) { Guard.NotNull(data); return WithWebSocket(b => b.WithJsonMessage(data, delayMs)); } /// /// Configure WebSocket response with binary message /// /// Binary payload /// Optional delay before sending (milliseconds) /// Response builder for chaining public IResponseBuilder WithWebSocketBinaryMessage(byte[] data, int? delayMs = null) { Guard.NotNull(data); return WithWebSocket(b => b.WithBinaryMessage(data, delayMs)); } /// /// Configure dynamic WebSocket messages via callback /// /// Async handler to generate messages based on request /// Response builder for chaining public IResponseBuilder WithWebSocketCallback( Func>> handler) { Guard.NotNull(handler); WithWebSocketUsed = true; WebSocketCallbackAsync = handler; return this; } /// /// Configure dynamic WebSocket messages via synchronous callback /// /// Handler to generate messages based on request /// Response builder for chaining public IResponseBuilder WithWebSocketCallback( Func> handler) { Guard.NotNull(handler); return WithWebSocketCallback(req => Task.FromResult(handler(req)) ); } /// /// Enable transformer for WebSocket messages /// /// Whether to apply transformer /// Transformer type (Handlebars or Scriban) /// Response builder for chaining public IResponseBuilder WithWebSocketTransformer( bool use = true, TransformerType transformerType = TransformerType.Handlebars) { if (WebSocketResponse != null) { WebSocketResponse.UseTransformer = use; WebSocketResponse.TransformerType = transformerType; } return this; } /// /// Configure WebSocket close frame for graceful disconnect /// /// WebSocket close code (default 1000 = normal) /// Optional close reason /// Response builder for chaining public IResponseBuilder WithWebSocketClose(int closeCode = 1000, string? reason = null) { if (WebSocketResponse != null) { WebSocketResponse.CloseCode = closeCode; WebSocketResponse.CloseMessage = reason; } return this; } /// /// Configure WebSocket subprotocol for negotiation /// /// Subprotocol name /// Response builder for chaining public IResponseBuilder WithWebSocketSubprotocol(string subprotocol) { Guard.NotNullOrWhiteSpace(subprotocol); WebSocketSubprotocol = subprotocol; if (WebSocketResponse != null) { WebSocketResponse.Subprotocol = subprotocol; } return this; } /// /// Auto-close WebSocket connection after delay if no messages /// /// Delay before auto-close (milliseconds) /// Response builder for chaining public IResponseBuilder WithWebSocketAutoClose(int delayMs) { Guard.GreaterThan(delayMs, 0); if (WebSocketResponse != null) { WebSocketResponse.AutoCloseDelayMs = delayMs; } return this; } } ``` --- ## 5. WebSocket Response Builder (WireMock.Net.Minimal) ### File: `ResponseBuilders/WebSocketResponseBuilder.cs` ```csharp // Copyright © WireMock.Net using System; using System.Collections.Generic; using Newtonsoft.Json; using Stef.Validation; using WireMock.Models; using WireMock.Transformers; using WireMock.Types; namespace WireMock.ResponseBuilders; /// /// Fluent builder for WebSocket responses /// public class WebSocketResponseBuilder : IWebSocketResponseBuilder { private readonly List _messages = new(); private bool _useTransformer; private TransformerType _transformerType = TransformerType.Handlebars; private int? _closeCode = 1000; private string? _closeMessage; private string? _subprotocol; private int? _autoCloseDelayMs; /// /// Add a text message /// public IWebSocketResponseBuilder WithMessage(string message, int? delayMs = null) { Guard.NotNullOrWhiteSpace(message); _messages.Add(new WebSocketMessage { BodyAsString = message, DelayMs = delayMs ?? 0, IsText = true, Id = Guid.NewGuid().ToString() }); return this; } /// /// Add binary message /// public IWebSocketResponseBuilder WithBinaryMessage(byte[] data, int? delayMs = null) { Guard.NotNull(data); _messages.Add(new WebSocketMessage { BodyAsBytes = data, DelayMs = delayMs ?? 0, IsText = false, Id = Guid.NewGuid().ToString() }); return this; } /// /// Add JSON message (auto-serialized) /// public IWebSocketResponseBuilder WithJsonMessage(object data, int? delayMs = null) { Guard.NotNull(data); var json = JsonConvert.SerializeObject(data); _messages.Add(new WebSocketMessage { BodyAsString = json, DelayMs = delayMs ?? 0, IsText = true, Id = Guid.NewGuid().ToString() }); return this; } /// /// Enable message transformation /// public IWebSocketResponseBuilder WithTransformer( bool use = true, TransformerType transformerType = TransformerType.Handlebars) { _useTransformer = use; _transformerType = transformerType; return this; } /// /// Configure connection close /// public IWebSocketResponseBuilder WithClose(int closeCode = 1000, string? reason = null) { _closeCode = closeCode; _closeMessage = reason; return this; } /// /// Set subprotocol for negotiation /// public IWebSocketResponseBuilder WithSubprotocol(string subprotocol) { Guard.NotNullOrWhiteSpace(subprotocol); _subprotocol = subprotocol; return this; } /// /// Auto-close after delay if no more messages /// public IWebSocketResponseBuilder WithAutoClose(int delayMs) { Guard.GreaterThan(delayMs, 0); _autoCloseDelayMs = delayMs; return this; } /// /// Build the WebSocket response /// public IWebSocketResponse Build() { return new WebSocketResponse { Messages = _messages, UseTransformer = _useTransformer, TransformerType = _transformerType, CloseCode = _closeCode, CloseMessage = _closeMessage, Subprotocol = _subprotocol, AutoCloseDelayMs = _autoCloseDelayMs }; } } ``` --- ## 6. Interfaces (WireMock.Net.Abstractions) ### File: `Models/IWebSocketMessage.cs` ```csharp // Copyright © WireMock.Net namespace WireMock.Models; /// /// Represents a WebSocket message to be sent /// public interface IWebSocketMessage { int DelayMs { get; set; } string? BodyAsString { get; set; } byte[]? BodyAsBytes { get; set; } bool IsText { get; set; } string? Id { get; set; } string? CorrelationId { get; set; } } ``` ### File: `Models/IWebSocketResponse.cs` ```csharp // Copyright © WireMock.Net using WireMock.Transformers; using WireMock.Types; namespace WireMock.Models; /// /// Defines WebSocket response configuration /// public interface IWebSocketResponse { List Messages { get; set; } bool UseTransformer { get; set; } TransformerType TransformerType { get; set; } int? CloseCode { get; set; } string? CloseMessage { get; set; } string? Subprotocol { get; set; } int? AutoCloseDelayMs { get; set; } } ``` ### File: `BuilderExtensions/WebSocketResponseBuilder.cs` ```csharp // Copyright © WireMock.Net using System; using System.Collections.Generic; using WireMock.Models; using WireMock.Transformers; using WireMock.Types; // ReSharper disable once CheckNamespace namespace WireMock.ResponseBuilders; /// /// Interface for WebSocket response builder /// public interface IWebSocketResponseBuilder { IWebSocketResponseBuilder WithMessage(string message, int? delayMs = null); IWebSocketResponseBuilder WithBinaryMessage(byte[] data, int? delayMs = null); IWebSocketResponseBuilder WithJsonMessage(object data, int? delayMs = null); IWebSocketResponseBuilder WithTransformer(bool use = true, TransformerType transformerType = TransformerType.Handlebars); IWebSocketResponseBuilder WithClose(int closeCode = 1000, string? reason = null); IWebSocketResponseBuilder WithSubprotocol(string subprotocol); IWebSocketResponseBuilder WithAutoClose(int delayMs); IWebSocketResponse Build(); } ``` --- ## 7. Integration Points ### Update to `Response.cs` base class ```csharp // In Response.cs, add to IResponseBuilder interface implementation: public interface IResponseBuilder { // ... existing members ... IResponseBuilder WithWebSocket(Action configure); IResponseBuilder WithWebSocketMessage(string message, int? delayMs = null); IResponseBuilder WithWebSocketJsonMessage(object data, int? delayMs = null); IResponseBuilder WithWebSocketBinaryMessage(byte[] data, int? delayMs = null); IResponseBuilder WithWebSocketCallback(Func>> handler); IResponseBuilder WithWebSocketCallback(Func> handler); IResponseBuilder WithWebSocketTransformer(bool use = true, TransformerType transformerType = TransformerType.Handlebars); IResponseBuilder WithWebSocketClose(int closeCode = 1000, string? reason = null); IResponseBuilder WithWebSocketSubprotocol(string subprotocol); IResponseBuilder WithWebSocketAutoClose(int delayMs); } ``` ### Update to `Request.cs` base class ```csharp // In Request.cs, add to IRequestBuilder interface implementation: public interface IRequestBuilder { // ... existing members ... IRequestBuilder WithWebSocketUpgrade(); IRequestBuilder WithWebSocketPath(string path); IRequestBuilder WithWebSocketSubprotocol(string subprotocol); IRequestBuilder WithWebSocketVersion(string version = "13"); IRequestBuilder WithWebSocketOrigin(string origin); } ``` --- ## 8. Unit Test Templates ### File: `Tests/ResponseBuilders/WebSocketResponseBuilderTests.cs` ```csharp using Xunit; using WireMock.ResponseBuilders; namespace WireMock.Net.Tests.ResponseBuilders; public class WebSocketResponseBuilderTests { [Fact] public void Build_WithSingleMessage_ReturnsValidResponse() { // Arrange var builder = new WebSocketResponseBuilder(); // Act var response = builder .WithMessage("Hello World") .Build(); // Assert Assert.NotNull(response); Assert.Single(response.Messages); Assert.Equal("Hello World", response.Messages[0].BodyAsString); } [Fact] public void Build_WithMultipleMessages_MaintainsOrder() { // Arrange var builder = new WebSocketResponseBuilder(); // Act var response = builder .WithMessage("First", 0) .WithMessage("Second", 100) .WithMessage("Third", 200) .Build(); // Assert Assert.Equal(3, response.Messages.Count); Assert.Equal("First", response.Messages[0].BodyAsString); Assert.Equal("Second", response.Messages[1].BodyAsString); Assert.Equal("Third", response.Messages[2].BodyAsString); } [Fact] public void Build_WithJsonMessage_SerializesObject() { // Arrange var builder = new WebSocketResponseBuilder(); var testData = new { id = 1, name = "test" }; // Act var response = builder .WithJsonMessage(testData) .Build(); // Assert Assert.Single(response.Messages); Assert.Contains("\"id\"", response.Messages[0].BodyAsString); } [Fact] public void Build_WithTransformer_SetsTransformerFlag() { // Arrange & Act var response = new WebSocketResponseBuilder() .WithMessage("{{request.path}}") .WithTransformer() .Build(); // Assert Assert.True(response.UseTransformer); } [Fact] public void Build_WithClose_SetsCloseCode() { // Arrange & Act var response = new WebSocketResponseBuilder() .WithMessage("Closing") .WithClose(1001, "Going away") .Build(); // Assert Assert.Equal(1001, response.CloseCode); Assert.Equal("Going away", response.CloseMessage); } } ``` --- ## Quick Start Template ```csharp // Basic echo server server.Given(Request.Create().WithWebSocketPath("/echo")) .RespondWith(Response.Create() .WithWebSocket(ws => ws .WithMessage("Echo server ready") ) .WithWebSocketCallback(async request => { return new[] { new WebSocketMessage { BodyAsString = $"Echo: {request.Body}" } }; }) ); // Real-time notifications server.Given(Request.Create() .WithWebSocketPath("/notifications") .WithWebSocketSubprotocol("notifications")) .RespondWith(Response.Create() .WithWebSocketSubprotocol("notifications") .WithWebSocket(ws => ws .WithJsonMessage(new { type = "connected" }, delayMs: 0) .WithJsonMessage(new { type = "notification", message = "New message" }, delayMs: 2000) .WithTransformer() .WithClose(1000, "Session ended") ) ); // Data streaming server.Given(Request.Create().WithWebSocketPath("/stream")) .RespondWith(Response.Create() .WithWebSocketCallback(async request => { var messages = new List(); for (int i = 0; i < 5; i++) { messages.Add(new WebSocketMessage { BodyAsString = $"{{\"index\":{i},\"timestamp\":\"{{now}}\"}}", DelayMs = i * 1000, IsText = true }); } return messages; }) .WithWebSocketTransformer() ); ``` --- This implementation guide provides all the necessary templates to implement WebSocket support following WireMock.Net's established fluent interface patterns.