I'm trying to stream data to a client over HTTP. To achieve this I'm using a WCF service with a WebHttpBinding. The problem is that the System.IO.Stream that my operation returns is closed before I can write something to it. I'd like to keep the stream open until data needs to be written to it. Usually this will not be more than half a minute.
In the service request method I create a new instance of System.IO.MemoryStream I put that into a collection of all streams and return it as the function output. Later on when there is audio data available I'm writing to all streams that are in the collection. But by then all the the requests have been closed. When I go to the endpoint url I get the browsers standard player completely greyed out. I also tested with a REST client, it showed me that the request closes immediately after the return statement.
The problem is that we use the Libspotify SDK to retrieve music. This sends 8192 bytes of PCM data per cycle. We would like to make it possible for users to play their music from a Chromecast device. The Chromecast doesn't support PCM data that's why we convert it to MP3 with libmp3lame and then send it through the output stream to the Chromecast. For this approach to work we need the connection to stay alive even though there is no actual data being send through the Stream.
The Libspotify music delivery callback can be found here.
This is how I set-up the service:
/// <summary>
/// The WCF service host.
/// </summary>
private ServiceHost ServiceHost;
/// <summary>
/// Start the HTTP WCF service.
/// </summary>
public void startListening()
{
if (ServiceHost == null)
{
ServiceHost = new ServiceHost(typeof(StreamingService));
var binding = new WebHttpBinding(WebHttpSecurityMode.None);
binding.TransferMode = TransferMode.StreamedResponse;
var endpoint = ServiceHost.AddServiceEndpoint(typeof(StreamingContract), binding, new Uri(streamAddress));
endpoint.EndpointBehaviors.Add(new WebHttpBehavior());
ServiceHost.Open();
}
}
This is the Service implementation:
[ServiceContract(Name="StreamingContract")]
interface StreamingContract
{
[WebGet(UriTemplate="audio")]
[OperationContract()]
Stream Audio();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
IncludeExceptionDetailInFaults = true)]
public class StreamingService : StreamingContract
{
public System.IO.Stream Audio()
{
var stream = new System.IO.MemoryStream();
App.Logic.streaming.streams.Add(stream);
WebOperationContext.Current.OutgoingResponse.ContentType = "audio/mp3";
WebOperationContext.Current.OutgoingResponse.ContentLength = 1000;
return stream;
}
}
I also tried setting: [OperationContext(AutoDisposeParameter=false)] on Audio() in the ServiceContract. This only started throwing a System.InvalidOperationException. I also thought maybe it's a problem that the content length is unknown, that didn't help either.
Your service is doing exactly what it should - returning an empty stream and then closing the connection.
It sounds like you want to wait for that stream to asynchronously be filled. In order to do that, you'll have to implement some kind of callback. You should look into the Task.Run() method, as that would be the standard way of implementing asynchronous logic in .NET.
Hope you can use this sample as an answer. In this sample you can PUT an stream async to the server. Solution is tested and verified.
Here is an example of an HTTP WCF service(server) hosting the service with async setup:
Uri baseAddress = new Uri("http://localhost:8000/Service1/");
// Step 2 Create a ServiceHost instance to host the service
using (ServiceHost selfHost = new ServiceHost(typeof(Service1), baseAddress)) // type of class that implements service contract, and base address of service.
{
try
{
WebHttpBinding binding = new WebHttpBinding();
//BasicHttpBinding binding = new BasicHttpBinding();
binding.TransferMode = TransferMode.Streamed;
binding.MaxReceivedMessageSize = int.MaxValue; //"1000000000000"
binding.ReceiveTimeout = new TimeSpan(1, 0, 0); //"01:00:00";
binding.SendTimeout = new TimeSpan(1, 0, 0); //"01:00:00";
//binding.ReaderQuotas. = int.MaxValue;
// Step 3 Add a service endpoint to host. Endpoint consist of address, binding and service contract.
// Note this is optional in Framework 4.0 and upward. generate auto default.
selfHost.AddServiceEndpoint(typeof(IService1), binding, "").EndpointBehaviors.Add(new WebHttpBehavior()); // service contract interface, binding, address
// Step 5 Start the service.
// Open host to listen for incoming messages.
selfHost.Open();
Console.WriteLine("The service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
// Close the ServiceHostBase to shutdown the service.
selfHost.Close();
}
catch (CommunicationException ce)
{
Console.WriteLine("An exception occurred: {0}", ce.Message);
selfHost.Abort();
}
}
}
}
}
Here is the actual service interface impl:
[ServiceContract]
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public interface IService1
{
/// <summary>
/// An asynchronous service side upload operation.
/// </summary>
/// <param name="token">An application arbitrary piece of data. Can be used for request obfuscation.</param>
/// <param name="data">The data being uploaded.</param>
/// <param name="callback">Callback for async pattern, client does not pass this.</param>
/// <param name="asyncState">User state for async pattern, client does not pass this.</param>
/// <remarks>
/// The <paramref name="token"/> parameter is the only parameter passed in the URL by the client. The <paramref name="data"/>
/// parameter is the request body, the file being uploaded.
/// </remarks>
/// <returns></returns>
[OperationContract(AsyncPattern = true)]
[WebInvoke(Method = "PUT", UriTemplate = "asyncupload/")]
IAsyncResult BeginAsyncUpload(Stream data, AsyncCallback callback, object asyncState);
/// <summary>
/// Ends the asynchonous operation initiated by the call to <see cref="BeginAsyncUpload"/>.
/// </summary>
/// <remarks>
/// This is called by the WCF framework service side. NOTE: There is no <see cref="OperationContractAttribute"/> decorating
/// this method.
/// </remarks>
/// <param name="ar"></param>
void EndAsyncUpload(IAsyncResult ar);
}
And the implementation:
public class Service1 : IService1
{
/// <summary>
/// <see cref="IUpload.Upload"/>
/// </summary>
/// <param name="token">This parameter is ignored.</param>
/// <param name="data">Data being uploaded.</param>
/// <param name="callback">Async callback.</param>
/// <param name="asyncState">Async user state.</param>
public IAsyncResult BeginAsyncUpload(Stream data, AsyncCallback callback, object asyncState)
{
return new CompletedAsyncResult<Stream>(data);
}
/// <summary>
/// <see cref="IUpload.EndAsyncUpload"/>
/// </summary>
public void EndAsyncUpload(IAsyncResult ar)
{
Stream data = ((CompletedAsyncResult<Stream>)ar).Data;
_streamToFile(data);
}
/// <summary>
/// Writes the uploaded stream to a file.
/// </summary>
/// <remarks>
/// This function is just to prove a test. This simple saves the uploaded data into a file named "upload.dat" in a subdirectory
/// whose name is created by a generated guid.
/// </remarks>
private static void _streamToFile(Stream data)
{
// create name of subdirectory
string subDir = Guid.NewGuid().ToString("N");
// get full path to and create the directory to save file in
string uploadDir = Path.Combine(Path.GetDirectoryName(typeof(Service1).Assembly.Location), subDir);
Directory.CreateDirectory(uploadDir);
// 64 KiB buffer
byte[] buff = new byte[0x10000];
// save the file in chunks
using (FileStream fs = new FileStream(Path.Combine(uploadDir, "upload.xml"), FileMode.Create))
{
int bytesRead = data.Read(buff, 0, buff.Length);
while (bytesRead > 0)
{
fs.Write(buff, 0, bytesRead);
bytesRead = data.Read(buff, 0, buff.Length);
}
}
}
In addition add an class in this project with the following content:
internal class CompletedAsyncResult<T> : IAsyncResult
{
T data;
public CompletedAsyncResult(T data)
{ this.data = data; }
public T Data
{ get { return data; } }
#region IAsyncResult Members
public object AsyncState
{ get { return (object)data; } }
public WaitHandle AsyncWaitHandle
{ get { throw new Exception("The method or operation is not implemented."); } }
public bool CompletedSynchronously
{ get { return true; } }
public bool IsCompleted
{ get { return true; } }
#endregion
}
internal class AsyncResultNoResult : IAsyncResult
{
// Fields set at construction which never change while
// operation is pending
private readonly AsyncCallback m_AsyncCallback;
private readonly Object m_AsyncState;
// Fields set at construction which do change after
// operation completes
private const Int32 c_StatePending = 0;
private const Int32 c_StateCompletedSynchronously = 1;
private const Int32 c_StateCompletedAsynchronously = 2;
private Int32 m_CompletedState = c_StatePending;
// Field that may or may not get set depending on usage
private ManualResetEvent m_AsyncWaitHandle;
// Fields set when operation completes
private Exception m_exception;
public AsyncResultNoResult(AsyncCallback asyncCallback, Object state)
{
m_AsyncCallback = asyncCallback;
m_AsyncState = state;
}
public void SetAsCompleted(
Exception exception, Boolean completedSynchronously)
{
// Passing null for exception means no error occurred.
// This is the common case
m_exception = exception;
// The m_CompletedState field MUST be set prior calling the callback
Int32 prevState = Interlocked.Exchange(ref m_CompletedState,
completedSynchronously ? c_StateCompletedSynchronously :
c_StateCompletedAsynchronously);
if (prevState != c_StatePending)
throw new InvalidOperationException(
"You can set a result only once");
// If the event exists, set it
if (m_AsyncWaitHandle != null) m_AsyncWaitHandle.Set();
// If a callback method was set, call it
if (m_AsyncCallback != null) m_AsyncCallback(this);
}
public void EndInvoke()
{
// This method assumes that only 1 thread calls EndInvoke
// for this object
if (!IsCompleted)
{
// If the operation isn't done, wait for it
AsyncWaitHandle.WaitOne();
AsyncWaitHandle.Close();
m_AsyncWaitHandle = null; // Allow early GC
}
// Operation is done: if an exception occured, throw it
if (m_exception != null) throw m_exception;
}
#region Implementation of IAsyncResult
public Object AsyncState { get { return m_AsyncState; } }
public Boolean CompletedSynchronously
{
get
{
return Thread.VolatileRead(ref m_CompletedState) ==
c_StateCompletedSynchronously;
}
}
public WaitHandle AsyncWaitHandle
{
get
{
if (m_AsyncWaitHandle == null)
{
Boolean done = IsCompleted;
ManualResetEvent mre = new ManualResetEvent(done);
if (Interlocked.CompareExchange(ref m_AsyncWaitHandle,
mre, null) != null)
{
// Another thread created this object's event; dispose
// the event we just created
mre.Close();
}
else
{
if (!done && IsCompleted)
{
// If the operation wasn't done when we created
// the event but now it is done, set the event
m_AsyncWaitHandle.Set();
}
}
}
return m_AsyncWaitHandle;
}
}
public Boolean IsCompleted
{
get
{
return Thread.VolatileRead(ref m_CompletedState) !=
c_StatePending;
}
}
#endregion
}
internal class AsyncResult<TResult> : AsyncResultNoResult
{
// Field set when operation completes
private TResult m_result = default(TResult);
public AsyncResult(AsyncCallback asyncCallback, Object state) :
base(asyncCallback, state) { }
public void SetAsCompleted(TResult result,
Boolean completedSynchronously)
{
// Save the asynchronous operation's result
m_result = result;
// Tell the base class that the operation completed
// sucessfully (no exception)
base.SetAsCompleted(null, completedSynchronously);
}
new public TResult EndInvoke()
{
base.EndInvoke(); // Wait until operation has completed
return m_result; // Return the result (if above didn't throw)
}
}
Then the client impl:
try
{
//string txtDescription = "Test";
string txtFileName = "Invoice_50000.xml";
//byte[] fileToSend = File.ReadAllBytes(txtFileName)
// Create the REST request.
string url = "http://localhost:8000/Service1/";//ConfigurationManager.AppSettings["serviceUrl"];
//string requestUrl = string.Format("{0}/Upload/{1}/{2}", url, System.IO.Path.GetFileName(txtFileName), txtDescription);
/* Asynchronous */
string requestUrl = string.Format("{0}/asyncupload/", url);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(requestUrl);
using (FileStream inputStream = File.Open(txtFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
//new BufferedStream
//new Buffer
request.SendChunked = true;
request.AllowWriteStreamBuffering = false;
request.Method = "PUT";
request.ContentType = "application/octet-stream";
//request.ContentType = MediaTypeNames.Application.Octet
request.ContentLength = inputStream.Length;
/* BEGIN: Solution with chunks */
// 64 KB buffer
byte[] chunkBuffer = new byte[0x10000];
Stream st = request.GetRequestStream();
// as the file is streamed up in chunks, the server will be processing the file
int bytesRead = inputStream.Read(chunkBuffer, 0, chunkBuffer.Length);
while (bytesRead > 0)
{
st.Write(chunkBuffer, 0, bytesRead);
bytesRead = inputStream.Read(chunkBuffer, 0, chunkBuffer.Length);
}
st.Close();
}
try
{
HttpWebResponse resp = (HttpWebResponse)request.GetResponse();
Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);
resp.Close();
}
catch (System.Exception)
{
//TODO: error handling here.
}
/* END: Solution with chunks */
}
Related
I couldn't find a satisfying answer to that question.
I'm trying to migrate my code from .NET Framework into .NET Core, and I ran into problems with my .ashx files.
Basically there are compile errors (Because I need to update my used NuGet Packages).
but even before that, I'm trying to figure out - Can I even use .ashx files inside .NET Core proejct?
Are there any changes I need to know about? Or should it be identical to .NET Framework (Besides packages).
Thanks!
Simply put, no.
Long answer:
All .ashx files have a background class that inherits either from System.Web.IHttpHandler or System.Web.IHttpAsyncHandler.
Now, if your handlers are programmed well, most of what you have to do is declare those interfaces in .NET Core:
namespace System.Web
{
[System.Runtime.InteropServices.ComVisible(true)]
public interface IAsyncResult
{
bool IsCompleted { get; }
System.Threading.WaitHandle AsyncWaitHandle { get; }
// Return value:
// A user-defined object that qualifies or contains information about an asynchronous
// operation.
object AsyncState { get; }
// Return value:
// true if the asynchronous operation completed synchronously; otherwise, false.
bool CompletedSynchronously { get; }
}
public interface IHttpAsyncHandler : IHttpHandler
{
// Return value:
// An System.IAsyncResult that contains information about the status of the process.
IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData);
// An System.IAsyncResult that contains information about the status of the process.
void EndProcessRequest(IAsyncResult result);
}
public interface IHttpHandler
{
// true if the System.Web.IHttpHandler instance is reusable; otherwise, false.
bool IsReusable { get; }
void ProcessRequest(Microsoft.AspNetCore.Http.HttpContext context);
}
}
And automatically rename System.Web.HttpContext into Microsoft.AspNetCore.Http.HttpContext.
Then you need to remove all references to System.Web.HttpContext.Current (which you wouldn't need to do if you programmed your handlers properly).
If that is a lot of work, my answer here
https://stackoverflow.com/a/40029302/155077
suggest a dirty workaround (basically, spoofing System.Web.HttpContext.Current).
Then, all you need to do is search to root path recursively for the ashx-file-paths, parse those files with regex for the corresponding classes (or do it manually if you have few) and add them as endpoint in .NET Core. Either you can just instanciate the class (singleton IsReusable = true), or you can write a handler that creates a new instance of class, and then calls ProcessRequest in said class.
You'll need to fix all compilation problems, though.
You can also parse the paths in the source project once, and generate the middleware/endpoint-injection code programmatically once.
That's probably the best option you have for large projects.
Though, you are in luck if you have ashx-handlers.
But don't just start just yet.
If you have aspx-pages or ascx-controls, you are definitely out of luck, and don't need to start with the .ashx-handlers in the first place - unless your aspx/ascx usage is so tiny, you can replace them fairly quickly.
Still, if you just need to port a lot of plain raw .ashx handlers producing json/xml, this method can work excellently.
Oh, also note that you need to change context.Response.OutputStream with context.Response.Body.
You might also have to change everything to async (if it isn't already), or you need to allow synchronous methods in the .NET-Core application startup.
Here's how to extract data:
namespace FirstTestApp
{
public class EndpointData
{
public string Path;
public string Class;
public string Language;
}
public static class HandlerFinder
{
private static System.Collections.Generic.List<EndpointData> FindAllHandlers()
{
string searchPath = #"D:\username\Documents\Visual Studio 2017\TFS\COR-Basic-V4\Portal\Portal";
searchPath = #"D:\username\Documents\Visual Studio 2017\TFS\COR-Basic\COR-Basic\Basic\Basic";
string[] ashxFiles = System.IO.Directory.GetFiles(searchPath, "*.ashx", System.IO.SearchOption.AllDirectories);
int searchPathLength = searchPath.Length;
System.Collections.Generic.List<EndpointData> ls = new System.Collections.Generic.List<EndpointData>();
foreach (string ashxFile in ashxFiles)
{
string input = #"<%# WebHandler Language=""VB"" Class=""Portal.Web.Data"" %>";
input = System.IO.File.ReadAllText(ashxFile, System.Text.Encoding.UTF8);
// http://regexstorm.net/tester
// <%# WebHandler Language="VB" Class="AmCharts.JSON" %>
System.Text.RegularExpressions.Match mClass = System.Text.RegularExpressions.Regex.Match(input, #"\<\%\#\s*WebHandler.*Class=""(?<class>.*?)"".*?\%\>", System.Text.RegularExpressions.RegexOptions.Multiline | System.Text.RegularExpressions.RegexOptions.IgnoreCase);
System.Text.RegularExpressions.Match mLanguage = System.Text.RegularExpressions.Regex.Match(input, #"\<\%\#\s*WebHandler.*Language=""(?<language>.*?)"".*?\%\>", System.Text.RegularExpressions.RegexOptions.Multiline | System.Text.RegularExpressions.RegexOptions.IgnoreCase);
string endpointPath = ashxFile.Substring(searchPathLength).Replace(System.IO.Path.DirectorySeparatorChar, '/');
string classname = mClass.Groups["class"].Value;
string languagename = mLanguage.Groups["language"].Value;
ls.Add(new EndpointData() { Path = endpointPath, Class = classname, Language = languagename });
}
System.Console.WriteLine("Finished " + ls.Count.ToString());
string json = System.Text.Json.JsonSerializer.Serialize(ls, ls.GetType(),
new System.Text.Json.JsonSerializerOptions
{
IncludeFields = true,
WriteIndented = true
});
// System.IO.File.WriteAllText(#"D:\PortalHandlers.json", json);
System.IO.File.WriteAllText(#"D:\BasicHandlers.json", json);
return ls; // 137 Portal + 578 Basic
}
public static int Test(string[] args)
{
FindAllHandlers();
return 0;
} // End Sub Test
} // End Class HandlerFinder
} // End Namespace FirstTestApp
And here's the Middleware with example handler "Ping.ashx".
namespace FirstTestApp
{
using Microsoft.AspNetCore.Http;
public class AshxOptions
{ }
public interface IHttpHandler
{
// true if the System.Web.IHttpHandler instance is reusable; otherwise, false.
bool IsReusable { get; }
void ProcessRequest(Microsoft.AspNetCore.Http.HttpContext context);
}
public class Ping
: IHttpHandler
{
public void ProcessRequest(Microsoft.AspNetCore.Http.HttpContext context)
{
context.Response.StatusCode = 200;
context.Response.ContentType = "text/plain; charset=utf-8;";
context.Response.WriteAsync(System.Environment.MachineName).Wait();
}
public bool IsReusable
{
get
{
return false;
}
} // End Property IsReusable
}
/// <summary>
/// Some Middleware that exposes something
/// </summary>
public class AshxMiddleware<T>
where T:IHttpHandler, new()
{
private readonly Microsoft.AspNetCore.Http.RequestDelegate m_next;
private readonly AshxOptions m_options;
private readonly ConnectionFactory m_factory;
/// <summary>
/// Creates a new instance of <see cref="AshxMiddleware"/>.
/// </summary>
public AshxMiddleware(
Microsoft.AspNetCore.Http.RequestDelegate next
, Microsoft.Extensions.Options.IOptions<object> someOptions
, ConnectionFactory db_factory
)
{
if (next == null)
{
throw new System.ArgumentNullException(nameof(next));
}
if (someOptions == null)
{
throw new System.ArgumentNullException(nameof(someOptions));
}
if (db_factory == null)
{
throw new System.ArgumentNullException(nameof(db_factory));
}
m_next = next;
m_options = (AshxOptions)someOptions.Value;
m_factory = db_factory;
}
/// <summary>
/// Processes a request.
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public async System.Threading.Tasks.Task InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context)
{
if (context == null)
{
throw new System.ArgumentNullException(nameof(context));
}
// string foo = XML.NameValueCollectionHelper.ToXml(httpContext.Request);
// System.Console.WriteLine(foo);
// httpContext.Response.StatusCode = 200;
// The Cache-Control is per the HTTP 1.1 spec for clients and proxies
// If you don't care about IE6, then you could omit Cache-Control: no-cache.
// (some browsers observe no-store and some observe must-revalidate)
context.Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate, max-age=0";
// Other Cache-Control parameters such as max-age are irrelevant
// if the abovementioned Cache-Control parameters (no-cache,no-store,must-revalidate) are specified.
// Expires is per the HTTP 1.0 and 1.1 specs for clients and proxies.
// In HTTP 1.1, the Cache-Control takes precedence over Expires, so it's after all for HTTP 1.0 proxies only.
// If you don't care about HTTP 1.0 proxies, then you could omit Expires.
context.Response.Headers["Expires"] = "-1, 0, Tue, 01 Jan 1980 1:00:00 GMT";
// The Pragma is per the HTTP 1.0 spec for prehistoric clients, such as Java WebClient
// If you don't care about IE6 nor HTTP 1.0 clients
// (HTTP 1.1 was introduced 1997), then you could omit Pragma.
context.Response.Headers["pragma"] = "no-cache";
// On the other hand, if the server auto-includes a valid Date header,
// then you could theoretically omit Cache-Control too and rely on Expires only.
// Date: Wed, 24 Aug 2016 18:32:02 GMT
// Expires: 0
context.Response.StatusCode = 200;
T foo = new T();
foo.ProcessRequest(context);
} // End Task AshxMiddleware
} // End Class AshxMiddleware
} // End Namespace
Then you still need a generic Endpoint Middleware, here:
namespace FirstTestApp
{
using FirstTestApp;
using Microsoft.AspNetCore.Builder;
// using Microsoft.AspNetCore.Diagnostics.HealthChecks;
// using Microsoft.Extensions.Diagnostics.HealthChecks;
// using Microsoft.AspNetCore.Routing;
// using Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Provides extension methods for <see cref="IEndpointRouteBuilder"/> to add health checks.
/// </summary>
public static class GenericEndpointRouteBuilderExtensions
{
/// <summary>
/// Adds a health checks endpoint to the <see cref="IEndpointRouteBuilder"/> with the specified template.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the health checks endpoint to.</param>
/// <param name="pattern">The URL pattern of the health checks endpoint.</param>
/// <returns>A convention routes for the health checks endpoint.</returns>
public static IEndpointConventionBuilder MapEndpoint<T>(
this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
string pattern)
{
if (endpoints == null)
{
throw new System.ArgumentNullException(nameof(endpoints));
}
return MapEndpointCore<T>(endpoints, pattern, null);
}
/// <summary>
/// Adds a health checks endpoint to the <see cref="IEndpointRouteBuilder"/> with the specified template and options.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the health checks endpoint to.</param>
/// <param name="pattern">The URL pattern of the health checks endpoint.</param>
/// <param name="options">A <see cref="SomeOptions"/> used to configure the health checks.</param>
/// <returns>A convention routes for the health checks endpoint.</returns>
public static IEndpointConventionBuilder MapEndpoint<T>(
this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
string pattern,
object options)
{
if (endpoints == null)
{
throw new System.ArgumentNullException(nameof(endpoints));
}
if (options == null)
{
throw new System.ArgumentNullException(nameof(options));
}
return MapEndpointCore<T>(endpoints, pattern, options);
}
private static IEndpointConventionBuilder MapEndpointCore<T>(
Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints
, string pattern
, object? options)
{
if (ReferenceEquals(typeof(T), typeof(SQLMiddleware)))
{
if (endpoints.ServiceProvider.GetService(typeof(ConnectionFactory)) == null)
{
throw new System.InvalidOperationException("Dependency-check failed. Unable to find service " +
nameof(ConnectionFactory) + " in " +
nameof(GenericEndpointRouteBuilderExtensions.MapEndpointCore)
+ ", ConfigureServices(...)"
);
}
}
object[] args = options == null ? System.Array.Empty<object>() :
new object[]
{
Microsoft.Extensions.Options.Options.Create(options)
}
;
Microsoft.AspNetCore.Http.RequestDelegate pipeline = endpoints.CreateApplicationBuilder()
.UseMiddleware<T>(args)
.Build();
return endpoints.Map(pattern, pipeline).WithDisplayName(typeof(T).AssemblyQualifiedName!);
}
} // End Class
}
And then you can set up the Endpoint with:
app.MapEndpoint<AshxMiddleware<Ping>>("/ping.ashx", new AshxOptions() { });
Keep in mind, middleware is async, and your ASHX-Handlers probably aren't.
That's gonna be a source of work-intensive problems (or deadlocks, if you just use .Wait() like in my example).
After upgrading the environment to target the .NET 4.6 framework from .NET 4.0, when AuditSoap is externally called it is referencing a null requestSoapMessage object due to what appears to be the ChainStream procedure not being initialised.
Does .NET 4.6 handle SOAP differently than previous versions of the framework? When I step through the code on .NET 4.0 it works as intended, with the ChainStream procedure being initialised (which then calls ProcessMessage). This doesn't happen on .NET 4.6 - the code base has not changed between upgrades so I believe the issue must reside with how .NET 4.6 handles it.
private static XmlDocument xmlRequest;
/// Gets the outgoing XML request
public static XmlDocument XmlRequest
{ get { return xmlRequest; } }
private static XmlDocument xmlResponse;
/// Gets the incoming XML response
public static XmlDocument XmlResponse
{ get { return xmlResponse; } }
private static SoapMessage requestSoapMessage;
/// Gets the outgoing soap message
public static SoapMessage RequestSoapMessage
{ get { return requestSoapMessage; } }
private static SoapMessage responseSoapMessage;
/// Gets the incoming soap message
public static SoapMessage ResponseSoapMessage
{ get { return responseSoapMessage; } }
/// Save the Stream representing the SOAP request
/// or SOAP response into a local memory buffer.
public override Stream ChainStream(Stream stream)
{
oldStream = stream;
newStream = new MemoryStream();
return newStream;
}
/// If the SoapMessageStage is such that the SoapRequest or
/// SoapResponse is still in the SOAP format to be sent or received,
/// save it to the xmlRequest or xmlResponse property.
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
xmlRequest = GetSoapEnvelope(newStream);
CopyStream(newStream, oldStream);
requestSoapMessage = message;
break;
case SoapMessageStage.BeforeDeserialize:
CopyStream(oldStream, newStream);
xmlResponse = GetSoapEnvelope(newStream);
responseSoapMessage = message;
break;
case SoapMessageStage.AfterDeserialize:
break;
}
}
public void AuditSoap()
{
try
{
string action = System.Web.HttpContext.Current.Request.RequestContext.RouteData.GetRequiredString("action");
//log the Request
logService.Audit(true,
LogArea.Data,
requestSoapMessage.MethodInfo.Name,
"Request",
action,
XmlRequest.InnerXml,
null,
null,
null,
XmlRequest.InnerXml);
//log the response
logService.Audit(true,
LogArea.Data,
responseSoapMessage.MethodInfo.Name,
"Response",
action,
XmlResponse.InnerXml,
null,
null,
null,
XmlResponse.InnerXml);
}
catch (Exception ex)
{
Trace.TraceError("Error in logging the web service.\n{0}", ex.ToJson());
}
}
/// Returns the XML representation of the Soap Envelope in the supplied stream.
/// Resets the position of stream to zero.
private XmlDocument GetSoapEnvelope(Stream stream)
{
XmlDocument xml = new XmlDocument();
stream.Position = 0;
StreamReader reader = new StreamReader(stream);
xml.LoadXml(reader.ReadToEnd());
stream.Position = 0;
return xml;
}
/// Copies a stream.
private void CopyStream(Stream from, Stream to)
{
TextReader reader = new StreamReader(from);
TextWriter writer = new StreamWriter(to);
writer.WriteLine(reader.ReadToEnd());
writer.Flush();
}
/// Included only because it must be implemented.
public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
return null;
}
/// Included only because it must be implemented.
public override object GetInitializer(Type WebServiceType)
{
return null;
}
/// Included only because it must be implemented.
public override void Initialize(object initializer)
{
}
I am implementing passing a web socket back from our api according to this url here;
http://blogs.msdn.com/b/youssefm/archive/2012/07/17/building-real-time-web-apps-with-asp-net-webapi-and-websockets.aspx
Now the idea is that the user will register for a web socket. This is done and works using the following code;
[HttpGet]
[Route("getsocket")]
public HttpResponseMessage GetWebSocket()
{
HttpContext.Current.AcceptWebSocketRequest(new TestWebSocketHandler());
return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
}
Then they make a call to the api to begin doing some specific functions, which will then report with message back down the same websocket.
private static WebSocketWrapper _socket;
[HttpGet]
[Route("begin")]
public async Task<IHttpActionResult> StartRunning(string itemid)
{
try
{
if (_socket == null ||
_socket.State() == WebSocketState.Aborted ||
_socket.State() == WebSocketState.Closed ||
_socket.State() == WebSocketState.None)
{
_socket = WebSocketWrapper.Create("wss://localhost:44301/api/v1/testcontroller/getsocket");
_socket.OnConnect(OnConnect);
_socket.OnDisconnect(OnDisconnect);
_socket.OnMessage(OnMessage);
await _socket.ConnectAsync();
}
//builds first message to be sent and sends it
_socket.QueueMessage(firstTest);
}
catch(Exception ex)
{
//logs error
}
return Ok();
}
So effectively the client cretes a new websocket connected to the server. They then call the second message to trigger the server to start a number of tests on the device passed. The server start the tests and broadcast the messages back down the socket (the json message model contains the deviceid, so the client can filter for relevent messages).
When they receive a message the client will then acknowledge it and the next test is done etc.
Now it works the first time I run it (after compilation). However, I want to be able to have multiple client connect to the websocket list (the solution is tabulated and the tests it will run may take a while, so its possible multiple tests will be ran at any one time). So I think it has something to do with the static WebSocketWrapper instance.
However, they have asked that a single websocket be used on the server, with a list of the devices being listened for. So in effect all messages are sent to all clients from the one server connection. The clients then filter out the messages they want to listen to based on the deviceid they pass.
When I try re-running, ore running a second test, Which is basically calling the getwebsocket and then the begin method, the code runs without error, but the onopen method never gets called? Its as though the socket just doesnt fire up?
Unfortunately we cannot use signalr as this is not specified
For reference the socket wrapper class is
public class WebSocketWrapper
{
private const int ReceiveChunkSize = 2048;
private const int SendChunkSize = 2048;
private readonly ClientWebSocket _ws;
private readonly Uri _uri;
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private readonly CancellationToken _cancellationToken;
private Action<WebSocketWrapper> _onConnected;
private Action<TestResultModel, WebSocketWrapper> _onMessage;
private Action<WebSocketWrapper> _onDisconnected;
//private static Queue<TestResultModel> _messageQueue = new Queue<TestResultModel>();
private static BlockingCollection<TestResultModel> _messageQueue = new BlockingCollection<TestResultModel>();
protected WebSocketWrapper(string uri)
{
_ws = new ClientWebSocket();
_ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(30);
_uri = new Uri(uri);
_cancellationToken = _cancellationTokenSource.Token;
}
/// <summary>
/// Creates a new instance.
/// </summary>
/// <param name="uri">The URI of the WebSocket server.</param>
/// <returns></returns>
public static WebSocketWrapper Create(string uri)
{
return new WebSocketWrapper(uri);
}
/// <summary>
/// Get the current state of the socket
/// </summary>
/// <returns>WebSocketState of the current _ws</returns>
public WebSocketState State()
{
return _ws.State;
}
/// <summary>
/// Disconnects from the websocket
/// </summary>
public async Task DisconnectAsync()
{
try
{
await _ws.CloseOutputAsync(
WebSocketCloseStatus.NormalClosure,
"Server has been closed by the disconnect method",
_cancellationToken);
CallOnDisconnected();
}
catch(Exception ex)
{
throw ex;
}
finally
{
_ws.Dispose();
}
}
/// <summary>
/// Connects to the WebSocket server.
/// </summary>
/// <returns></returns>
public async Task<WebSocketWrapper> ConnectAsync()
{
try
{
await _ws.ConnectAsync(_uri, _cancellationToken);
}
catch(Exception ex)
{
}
CallOnConnected();
RunInTask(() => ProcessQueueAsync());
RunInTask(() => StartListen());
return this;
}
/// <summary>
/// Set the Action to call when the connection has been established.
/// </summary>
/// <param name="onConnect">The Action to call.</param>
/// <returns></returns>
public WebSocketWrapper OnConnect(Action<WebSocketWrapper> onConnect)
{
_onConnected = onConnect;
return this;
}
/// <summary>
/// Set the Action to call when the connection has been terminated.
/// </summary>
/// <param name="onDisconnect">The Action to call</param>
/// <returns></returns>
public WebSocketWrapper OnDisconnect(Action<WebSocketWrapper> onDisconnect)
{
_onDisconnected = onDisconnect;
return this;
}
/// <summary>
/// Adds a message to the queu for sending
/// </summary>
/// <param name="message"></param>
public void QueueMessage(TestResultModel message)
{
//_messageQueue.Enqueue(message);
_messageQueue.Add(message);
}
/// <summary>
/// returns the size of the current message queue.
/// Usefult for detemning whether or not an messages are left in queue before closing
/// </summary>
/// <returns></returns>
public int QueueCount()
{
return _messageQueue.Count;
}
/// <summary>
/// Processes the message queue in order
/// </summary>
public async Task ProcessQueueAsync()
{
try
{
foreach(var current in _messageQueue.GetConsumingEnumerable())
{
await SendMessageAsync(current);
}
}
catch(Exception ex)
{
//TODO
}
}
/// <summary>
/// Set the Action to call when a messages has been received.
/// </summary>
/// <param name="onMessage">The Action to call.</param>
/// <returns></returns>
public WebSocketWrapper OnMessage(Action<TestResultModel, WebSocketWrapper> onMessage)
{
_onMessage = onMessage;
return this;
}
/// <summary>
/// Send a message to the WebSocket server.
/// </summary>
/// <param name="message">The message to send</param>
public async Task SendMessageAsync(TestResultModel result)
{
if (_ws.State != WebSocketState.Open)
{
throw new Exception("Connection is not open.");
}
var message = JsonConvert.SerializeObject(result);
var messageBuffer = Encoding.UTF8.GetBytes(message);
var messagesCount = (int)Math.Ceiling((double)messageBuffer.Length / SendChunkSize);
for (var i = 0; i < messagesCount; i++)
{
var offset = (SendChunkSize * i);
var count = SendChunkSize;
var lastMessage = ((i + 1) == messagesCount);
if ((count * (i + 1)) > messageBuffer.Length)
{
count = messageBuffer.Length - offset;
}
await _ws.SendAsync(new ArraySegment<byte>(messageBuffer, offset, count), WebSocketMessageType.Text, lastMessage, _cancellationToken);
}
}
private async Task StartListen()
{
var buffer = new byte[ReceiveChunkSize];
//part of a big hack, temporary solution
string prevResult = "";
try
{
while (_ws.State == WebSocketState.Open)
{
var stringResult = new StringBuilder();
WebSocketReceiveResult result;
do
{
result = await _ws.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationToken);
if (result.MessageType == WebSocketMessageType.Close)
{
await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
CallOnDisconnected();
}
else
{
var str = Encoding.UTF8.GetString(buffer, 0, result.Count);
stringResult.Append(str);
}
} while (!result.EndOfMessage);
if (!prevResult.Equals(stringResult.ToString()))
{
prevResult = stringResult.ToString();
CallOnMessage(stringResult);
}
}
}
catch (Exception ex)
{
CallOnDisconnected();
}
finally
{
_ws.Dispose();
}
}
private void CallOnMessage(StringBuilder stringResult)
{
if (_onMessage != null)
{
try
{
var message = JsonConvert.DeserializeObject<TestResultModel>(stringResult.ToString());
RunInTask(() => _onMessage(message, this));
}
catch (Exception ex)
{
//Ignore any other messages not TestResultModel
}
}
}
private void CallOnDisconnected()
{
if (_onDisconnected != null)
RunInTask(() => _onDisconnected(this));
}
private void CallOnConnected()
{
if (_onConnected != null)
RunInTask(() => _onConnected(this));
}
private static Task RunInTask(Action action)
{
return Task.Factory.StartNew(action);
}
}
As an update please see the debug screen taken when trying to call the websocket for the second time. As you can see it appears to be in the aborted state? (on first run obviousl its null). Any ideas?
A lot of the functionality you are looking is "easy" to implement using SignalR. As where you create a "Hub" where you can broadcast to all clients or simply choose a single client.
I take it that you have previous experience with websockets, so I think you should take a look! It is a bit magical and can be a hell to troubleshoot in bigger applications and services.
A simple chat tutorial can be found here
You can also take a look here for a person that has had problems sending notications to single clients using SignalR
I have a very simple WCF program where I have a simple self-host and a client running on the same computer. There is a single method which returns a System.IO.Stream, which is actually a serialized form of a simple string. (This could be any number of data types, but for the time being let's take it as a string).
Following is the code I use if you want to take a look at. SerializeData() and DeserializeData() are methods used to do just that, and works fine.
Host Service:
namespace HelloWCF1
{
[ServiceContract(Namespace = "http://My.WCF.Samples")]
public interface IService1
{
[OperationContract]
Stream GetString();
}
public class Service1 : IService1
{
//Simple String
public Stream GetString()
{
string str = "ABC";
Stream ms = new MemoryStream();
SerializeData<string>(str, ms);
return ms;
}
/// <summary>
/// Serialize an object of the type T to a Stream
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="objectToSerialize"></param>
/// <param name="str"></param>
public void SerializeData<T>(T objectToSerialize, Stream str)
{
BinaryFormatter bf = new BinaryFormatter();
try
{
bf.Serialize(str, objectToSerialize);
str.Position = 0;
}
catch (Exception)
{
}
}
/// <summary>
/// Deserialize a Stream
/// </summary>
/// <param name="dataToDeserialize"></param>
/// <returns></returns>
public object DeserializeData(Stream dataToDeserialize)
{
BinaryFormatter bf = new BinaryFormatter();
object ret = null;
try
{
ret = bf.Deserialize(dataToDeserialize);
}
catch (Exception)
{
}
return ret;
}
}
class Program
{
static void Main(string[] args)
{
Uri baseAddr = new Uri("http://localhost:8000/WCFSampleService");
//ServiceHost is created by defining Service Type and Base Address
using (ServiceHost svcHost = new ServiceHost(typeof(Service1), baseAddr))
{
//Trace message for service start
Console.WriteLine("Service Starting...");
//Adding an end point
svcHost.AddServiceEndpoint(typeof(IService1), new BasicHttpBinding(), "HelloWCF");
//Open service host
svcHost.Open();
Console.WriteLine("Press [Enter] to terminate.");
Console.ReadLine();
//Close service host
svcHost.Close();
}
}
}
}
Client Program:
namespace Client
{
[ServiceContract(Namespace = "http://My.WCF.Samples")]
public interface IService1
{
[OperationContract]
Stream GetString();
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private IService1 proxy = null;
private Stream memStr = new MemoryStream();
private void button1_Click(object sender, EventArgs e)
{
//Create end point
EndpointAddress epAddr = new EndpointAddress("http://localhost:8000/WCFSampleService/HelloWCF");
//Create proxy
proxy = ChannelFactory<IService1>.CreateChannel(new BasicHttpBinding(), epAddr);
//WCF Service Method is called to aquire the stream
try
{
memStr = proxy.GetString();
string str = (string)DeserializeData(memStr);
MessageBox.Show(str);
}
catch (CommunicationException commEx)
{
MessageBox.Show("Service Call Failed:" + commEx.Message);
}
}
/// <summary>
/// Serialize an object of the type T to a Stream
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="objectToSerialize"></param>
/// <param name="str"></param>
public static void SerializeData<T>(T objectToSerialize, Stream str)
{
BinaryFormatter bf = new BinaryFormatter();
try
{
bf.Serialize(str, objectToSerialize);
str.Position = 0;
}
catch (Exception)
{
}
}
/// <summary>
/// Deserialize a Stream
/// </summary>
/// <param name="dataToDeserialize"></param>
/// <returns></returns>
public static object DeserializeData(Stream dataToDeserialize)
{
BinaryFormatter bf = new BinaryFormatter();
object ret = null;
try
{
ret = bf.Deserialize(dataToDeserialize);
}
catch (Exception)
{
}
return ret;
}
}
}
Now, this works fine. It serializes a string into a Stream and sends using HTTP quite fine. However, I have a need to convert of cast this Stream into an object before sending. Simply put, I need the GetString() method to look like this:
public object GetString()
{
string str = "ABC";
Stream ms = new MemoryStream();
SerializeData<string>(str, ms);
return (object)ms;
}
However, when I do this, inside the Button1_Click event, at the line ***memStr = proxy.GetString();*** I get a CommunicationException. I work in a Japanese OS, so the exception message I get is in Japanese so I'll try to translate into English as best as I can. Forgive me if it is not very clear.
***Error occured in the reception of HTTP response concerning [url]http://localhost:8000/WCFSampleService/HelloWCF[/url]. Cause of the error could be Service End Point binding is not using an HTTP protocol. Or as a different cause, it is also possible that HTTP request context is suspended according to the server (as in the case of server being shut down). Please refer the server log for more details.***
What exactly is the problem and how can I get the program to work the way I want? It says to look at the log files but where can I find them?
Thanks in advance!
Would you not be better off just returning the string? Surely the string returned as a stream over an Http binding will be being Base64 encoded, which normally doubles the size of the data being returned!
Maybe you could consider TcpBinding as an alternative.
I have a classic asynchronous message dispatching problem. Essentially, I need to asynchronously dispatch messages and then capture the message response when the dispatch is complete. The problem is, I can't seem to figure out how to make any one request cycle self-expire and shortcircuit.
Here is a sample of the pattern I am using at the moment:
Defined delegate for invokation
private delegate IResponse MessageDispatchDelegate(IRequest request);
Dispatch messages with a callback
var dispatcher = new MessageDispatchDelegate(DispatchMessage);
dispatcher.BeginInvoke(requestMessage, DispatchMessageCallback, null);
Dispatch the message
private IResponse DispatchMessage(IRequest request)
{
//Dispatch the message and throw exception if it times out
}
Get results of dispatch as either a response or an exception
private void DispatchMessageCallback(IAsyncResult ar)
{
//Get result from EndInvoke(r) which could be IResponse or a Timeout Exception
}
What I can't figure out is how to cleanly implement the timeout/shortcircuit process in the DispatchMessage method. Any ideas would be appreciated
var dispatcher = new MessageDispatchDelegate(DispatchMessage);
var asyncResult = dispatcher.BeginInvoke(requestMessage, DispatchMessageCallback, null);
if (!asyncResult.AsyncWaitHandle.WaitOne(1000, false))
{
/*Timeout action*/
}
else
{
response = dispatcher.EndInvoke(asyncResult);
}
After lots of head-scratching I was finally able to find a solution for my original question. First off, let me say I got a lot of great responses and I tested all of them (commenting each with the results). The main problems were that all the proposed solutions led to either dead-locks (leading to 100% timeout scenario's) or made an otherwise Asyncronous process syncronous. I don't like answering my own question (first time ever), but in this case I took the advice of the StackOverflow FAQ since I've truely learned my own lesson and wanted to share it with the community.
In the end, I combined the proposed solutions with the invocation of delagates into alternate AppDomains. It's a bit more code and it's a little more expensive, but this avoids the dead-locks and allows fully asyncronous invocations which is what I required. Here are the bits...
First I needed something to invoke a delegate in another AppDomain
/// <summary>
/// Invokes actions in alternate AppDomains
/// </summary>
public static class DomainInvoker
{
/// <summary>
/// Invokes the supplied delegate in a new AppDomain and then unloads when it is complete
/// </summary>
public static T ExecuteInNewDomain<T>(Delegate delegateToInvoke, params object[] args)
{
AppDomain invocationDomain = AppDomain.CreateDomain("DomainInvoker_" + delegateToInvoke.GetHashCode());
T returnValue = default(T);
try
{
var context = new InvocationContext(delegateToInvoke, args);
invocationDomain.DoCallBack(new CrossAppDomainDelegate(context.Invoke));
returnValue = (T)invocationDomain.GetData("InvocationResult_" + invocationDomain.FriendlyName);
}
finally
{
AppDomain.Unload(invocationDomain);
}
return returnValue;
}
[Serializable]
internal sealed class InvocationContext
{
private Delegate _delegateToInvoke;
private object[] _arguments;
public InvocationContext(Delegate delegateToInvoke, object[] args)
{
_delegateToInvoke = delegateToInvoke;
_arguments = args;
}
public void Invoke()
{
if (_delegateToInvoke != null)
AppDomain.CurrentDomain.SetData("InvocationResult_" + AppDomain.CurrentDomain.FriendlyName,
_delegateToInvoke.DynamicInvoke(_arguments));
}
}
}
Second I needed something to orchestrate collection of the required parameters and collect/resolve the results. This will also define the timeout and worker processes which will be called asyncronously in an alternate AppDomain
Note: In my tests, I extended the dispatch worker method to take random amounts of time to observe that everything worked as expected in both timeout and non-timeout cases
public delegate IResponse DispatchMessageWithTimeoutDelegate(IRequest request, int timeout = MessageDispatcher.DefaultTimeoutMs);
[Serializable]
public sealed class MessageDispatcher
{
public const int DefaultTimeoutMs = 500;
/// <summary>
/// Public method called on one more many threads to send a request with a timeout
/// </summary>
public IResponse SendRequest(IRequest request, int timeout)
{
var dispatcher = new DispatchMessageWithTimeoutDelegate(SendRequestWithTimeout);
return DomainInvoker.ExecuteInNewDomain<Response>(dispatcher, request, timeout);
}
/// <summary>
/// Worker method invoked by the <see cref="DomainInvoker.ExecuteInNewDomain<>"/> process
/// </summary>
private IResponse SendRequestWithTimeout(IRequest request, int timeout)
{
IResponse response = null;
var dispatcher = new DispatchMessageDelegate(DispatchMessage);
//Request Dispatch
var asyncResult = dispatcher.BeginInvoke(request, null, null);
//Wait for dispatch to complete or short-circuit if it takes too long
if (!asyncResult.AsyncWaitHandle.WaitOne(timeout, false))
{
/* Timeout action */
response = null;
}
else
{
/* Invoked call ended within the timeout period */
response = dispatcher.EndInvoke(asyncResult);
}
return response;
}
/// <summary>
/// Worker method to do the actual dispatch work while being monitored for timeout
/// </summary>
private IResponse DispatchMessage(IRequest request)
{
/* Do real dispatch work here */
return new Response();
}
}
Third I need something to stand-in for the actual thing that is asyncronously triggering the dispatches
Note: This is just to demonstrate the asyncronous behaviours I required. In reality, the First and Second items above demonstrate the isolation of timeout behaviours on alternate threads. This just demonstrates how the above resources are used
public delegate IResponse DispatchMessageDelegate(IRequest request);
class Program
{
static int _responsesReceived;
static void Main()
{
const int max = 500;
for (int i = 0; i < max; i++)
{
SendRequest(new Request());
}
while (_responsesReceived < max)
{
Thread.Sleep(5);
}
}
static void SendRequest(IRequest request, int timeout = MessageDispatcher.DefaultTimeoutMs)
{
var dispatcher = new DispatchMessageWithTimeoutDelegate(SendRequestWithTimeout);
dispatcher.BeginInvoke(request, timeout, SendMessageCallback, request);
}
static IResponse SendRequestWithTimeout(IRequest request, int timeout = MessageDispatcher.DefaultTimeoutMs)
{
var dispatcher = new MessageDispatcher();
return dispatcher.SendRequest(request, timeout);
}
static void SendMessageCallback(IAsyncResult ar)
{
var result = (AsyncResult)ar;
var caller = (DispatchMessageWithTimeoutDelegate)result.AsyncDelegate;
Response response;
try
{
response = (Response)caller.EndInvoke(ar);
}
catch (Exception)
{
response = null;
}
Interlocked.Increment(ref _responsesReceived);
}
}
In retrospect, this approach has some unintended consequences. Since the worker method occurs in an alternate AppDomain, this adds addition protections for exceptions (although it can also hide them), allows you to load and unload other managed assemblies (if required), and allows you to define highly constrained or specialized security contexts. This requires a bit more productionization but provided the framework to answer my original question. Hope this helps someone.