Execute a server side program asynchronously with an asp.net mvc4 app - asp.net

is it possible to execute a server side program and get the output asynchronously.
i have this code that doing the job but synchronously:
suppose a c# program "program.exe" like this :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace testconsole
{
class Program
{
static void Main(string[] args)
{
for (int k = 0; k < 10; k++ )Console.WriteLine(k);
}
}
}
some view in the asp.net app like this :
<script >
function go()
{
var options = {
url: '/excute',
type: 'GET',
dataType: 'json'
}
//make call
$.ajax(options)
.then(function (data) {
console.log(data);
});
}
</script>
<input type="submit" onclick="go();" value="Go">
and the excute controller looks like this :
namespace myApp.Controllers
{
public class ExecuteController : Controller
{
//
// GET: /Execute
[HttpGet]
public JsonResult Index()
{
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "program.exe";
p.Start();
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
return Json(new { op = output }, JsonRequestBehavior.AllowGet);
}
}
}
All this is working fine, But ... from the client have to wait till the end of the program to display its outputs, is there any way to get those outputs as soon as they r created?
im sur i need to make some changes in the controller to make it possible, but how ???

Asp.Net MVC has the concept of an Async Controller that is suited to perform long-running tasks. It will help you by not locking a thread while you wait for out program to execute.
But to do what you are after I think you need to create you own Http Handler (probaby by implementing the IHttpHandler interface) that wraps the process and returns the results incrementally. This will not be trivial to do, but it should be possible.
A third viable alternative might be to use SignalR. That would be a fun project, but would still require much work I think.

The problem is primarily with communication between the IIS host process and your external process. You would need to facilitate some sort channel of communication to send "progress" events from the console application into the ASP.NET application.
A WCF client sending information via named pipes to a service hosted in the ASP.NET application would enable you to send messages into the application. You would host the service when the request is made and dynamically generate the name of pipe as a way to correlate to the initial request.
Once you get the updates in the application, you could then use something like SignalR to allow you to push the information back up the client.

Im back finally with an answer (not perfect i suppose). I used SignalR to get this done.
i created a messenger program (with c#) that will be the bridge between an asp.net mvc4 application and any console program that displays outputs.
the messenger will execute the program , then redirect his outputs to be send trough SignalR to the client.
if you are interested i've created a repo at github for this ,check this code here. I hope it will help someone one day.
i will be happy to talk about this code with you.

Related

How to make logs in application insight without using Task.Delay method?

using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApp8
{
class Program
{
static IServiceCollection services = new ServiceCollection()
.AddLogging(loggingBuilder => loggingBuilder.AddFilter<Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider>("", LogLevel.Trace))
.AddApplicationInsightsTelemetryWorkerService("Application_Key");
static IServiceProvider serviceProvider = services.BuildServiceProvider();
static ILogger<Program> logger = serviceProvider.GetRequiredService<ILogger<Program>>();
static TelemetryClient telemetryClient = serviceProvider.GetRequiredService<TelemetryClient>();
static void Main(string[] args)
{
using (telemetryClient.StartOperation<RequestTelemetry>("AppointmentPatientCommunication"))
{
logger.LogInformation("1st");
hero();
logger.LogError("2nd");
telemetryClient.TrackTrace("Here is the error");
telemetryClient.Flush();
}
}
static void hero()
{
using (telemetryClient.StartOperation<RequestTelemetry>("AppointmentPatientCommunication"))
{
logger.LogInformation("2nd");
telemetryClient.Flush();
}
}
}
}
I uploading this console application as my webjob to make a log in application insight. I am trying to avoid the use of task.delay() so that I can get real-time logging at perfect timing. I am uploading this webjob triggered manually, but I see no entry in my application insights. Could anyone help me out with this one?
Telemetry is not sent instantly. Telemetry items are batched and sent by the ApplicationInsights SDK. In Console apps, which exits right after calling Track() methods, telemetry may not be sent unless Flush() and Sleep/Delay is done before the app exits as shown in full example later in this article. Sleep is not required if you are using InMemoryChannel. There is an active issue regarding the need for Sleep which is tracked here: link.
So there are two types of channels: InMemoryChannel and ServerTelemetryChannel
For more details about the both the channels click on this link.
In my program to deal with the issue, I used InMemoryChannel. In the below code, I have shown a portion of code to show how I added it in my program.
static IServiceCollection services = new ServiceCollection()
.AddLogging(loggingBuilder => loggingBuilder.AddFilter<Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider>("", LogLevel.Trace))
.AddSingleton(typeof(ITelemetryChannel), new InMemoryChannel())
.AddApplicationInsightsTelemetryWorkerService("Application_Key");
The Nuget Package I am using is Microsoft.ApplicationInsights.Channel
Thanks to # Peter Bons for your comment, Which helps to fix the problem.
The Flush() method in the Telemetry Client is used to flush the in-memory buffer when the application is shutting down. Normally, the SDK delivers data every 30 seconds or when the buffer is full (500 items), and there is no need to invoke the Flush() method manually for web applications unless the program is ready to be shut down.
The TelemetryClient object's Flush() method sends all of the data it presently holds in a buffer to the App Insights service.
Application Insights will transfer your data in batches in the background to make better use of the network.
In most cases, you won't need to call Flush(). However, if you know the process will leave after that point, you should execute Flush() to ensure that all of the data gets transmitted.
Here, I have added the Thread.Sleep(); call after the Flush Statement.
static void Main(string[] args)
{
using (telemetryClient.StartOperation<RequestTelemetry>("AppointmentPatientCommunication"))
{
logger.LogInformation("1st");
hero();
logger.LogError("2nd");
telemetryClient.TrackTrace("Here is the error");
# Flush takes some times to memory buffer at the shutdown activity.
telemetryClient.Flush();
# By default Flush takes 30 Sec so you have to wait for 30 sec.
Thread.Sleep(5000);
}
}
static void hero()
{
using (telemetryClient.StartOperation<RequestTelemetry>("AppointmentPatientCommunication"))
{
logger.LogInformation("2nd");
# Flush takes some times to memory buffer at the shutdown activity.
telemetryClient.Flush();
# By default Flush takes 30 Sec so you have to wait for 30 sec.
Thread.Sleep(5000);
}
}
Results in AI:

How do I make a SignalR external reference to hub and not perform circular reference?

So, I'm trying to create a sample where there are the following components/features:
A hangfire server OWIN self-hosted from a Windows Service
SignalR notifications when jobs are completed
Github Project
I can get the tasks queued and performed, but I'm having a hard time sorting out how to then notify the clients (all currently, just until I get it working well) of when the task/job is completed.
My current issue is that I want the SignalR hub to be located in the "core" library SampleCore, but I don't see how to "register it" when starting the webapp SampleWeb. One way I've gotten around that is to create a hub class NotificationHubProxy that inherits the actual hub and that works fine for simple stuff (sending messages from one client to all).
In NotifyTaskComplete, I believe I can get the hub context and then send the message like so:
private void NotifyTaskComplete(int taskId)
{
try
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
if (hubContext != null)
{
hubContext.Clients.All.sendMessage(string.Format("Task {0} completed.", taskId));
}
}
catch (Exception ex)
{
}
}
BUT, I can't do that if NotificationHubProxy is the class being used as it's part of the SampleWeb library and referencing it from SampleCore would lead to a circular reference.
I know the major issue is the hub in the external assembly, but I can't for the life of me find a relevant sample that's using SignalR or MVC5 or setup in this particular way.
Any ideas?
So, the solution was to do the following two things:
I had to use the SignalR .NET client from the SampleCore assembly to create a HubConnection, to create a HubProxy to "NotificationHub" and use that to Invoke the "SendMessage" method - like so:
private void NotifyTaskComplete(string hostUrl, int taskId)
{
var hubConnection = new HubConnection(hostUrl);
var hub = hubConnection.CreateHubProxy("NotificationHub");
hubConnection.Start().Wait();
hub.Invoke("SendMessage", taskId.ToString()).Wait();
}
BUT, as part of creating that HubConnection - I needed to know the url to the OWIN instance. I decided to pass that a parameter to the task, retrieving it like:
private string GetHostAddress()
{
var request = this.HttpContext.Request;
return string.Format("{0}://{1}", request.Url.Scheme, request.Url.Authority);
}
The solution to having a Hub located in an external assembly is that the assembly needs to be loaded before the SignalR routing is setup, like so:
AppDomain.CurrentDomain.Load(typeof(SampleCore.NotificationHub).Assembly.FullName);
app.MapSignalR();
This solution for this part came from here.

Get full URL inside SignalR hub

I'm developing an user tracking solution using SignalR, as a fun project to learn SignalR, for ASP.NET MVC applications.
Currently i can track logged users and how long are they on a specific page. If they move to another page i track that also and the timer that SignalR is updating resets... Many other features are implemented or partially implemented.
The problem i'm facing is how to get the full url Controller/Action/Parameters
inside SignalR hub?
When i use HttpContext.Current.Request.Url the url is always /signalr/connect.
NOTE:
var hub = $.connection.myHub;
$.connection.hub.start();
is in the _Layout.cshtml.
UPDATE:
I've tried to use
var location = '#HttpContext.Current.Request.Url';
var hub = $.connection.myHub;
$.connection.hub.start().done(function () {
hub.setLocation(location);
});
And the location is passed correctly but I need it on the Connect() task not later.
Is it possible to do this?
UPDATE 2:
This approach doesn't work
var hub = $.connection.myHub;
$.connection.hub.start(function(){hub.setLocation(location)});
as the Connect() is called before.
In my hub i have several methods but i would like pass a value (in my case a location) to the Connect(), is that possible?
public class MyHub : Hub, IDisconnect, IConnected
{
public Task Connect()
{
//do stuff here
//and i would like to have the **location** value
}
public Task Disconnect()
{
//do stuff here
}
}
Update 3
Use QueryString to pass data before the Connect() occurs.
var location = '#HttpContext.Current.Request.Url';
var hub = $.connection.myHub;
$.connection.hub.qs = "location= + location;
$.connection.hub.start();
Passing data like your location value to Connect() is possible via a querystring parameter: SignalR: How to send data to IConnected.Connect()
Using query-string is not very secure, cause a hacker can forge JS code and send you wrong location breaking whatever logic you have behind it.
You can try to get this from owin-enviromment variables
var underlyingHttpContext =
Context.Request.Environment[typeof(HttpContextBase).FullName] as HttpContextBase;
Then extract whatever you need.
It will work on IIS, for non-IIS hosting look for other OWIN stuff https://github.com/aspnet/AspNetKatana/wiki/OWIN-Keys
You could pass it from your client js call to your hub as a parameter.

How to pass message between client & server over the web using SignalR

I heard SignalR is a good messaging library. I got some code for SignalR but I am not able to understand how it works.
JS
var hooking;
$(function() {
hooking = $.connection.hooking;
hooking.removeLead = function(ref) {
$("lead" + ref).remove();
};
$.connection.hub.start();
});
C#
// Hooking.cs (placed in application root)
public class Hooking : Hub
{
public void Submit(string jsonString)
{
var serializer = new JavaScriptSerializer();
var json = serializer.Deserialize<HookingLeadResult>(jsonString);
Clients.removeLead(json.Ref); // Remove lead from client hooking windows
// update lead gen
}
}
I have questions about the above code.
What does hooking mean ins $.connection.hooking;
Where is removeLead in hooking.removeLead
What will this do $.connection.hub.start(); ? What does it start? Which method it will invoke at the server side?
Who & how Submit method will be called at the server side? how to pass data from client side to server side. If possible please give me a url for good start for SignalR library.
The Javascript function hooking.removeLead will be invoked whenever you call Clients.removeLead(). All the bindings are done dynamically, between Javascript to C# and between C# and Javascript.
$.connection.hub.start() is actually the connect function. It will connect your client to the server. No messages can be sent or received until you do. The start function allows you to define a callback to be called when it's done connecting.
The Submit method at the server will be called whenever you do a hooking.submit(json) call on your client. For instance, as a result of the user filling in some form and clicking a button.
I recommend starting with the SignalR official wiki: http://www.asp.net/signalr

Cross browser scripting proxy

I am developing some client side Javascript that is using some JSON web services on a different domain. I have read that some browsers do not allow cross-domain scripting and that I should create a proxy on my local server to serve the data.
Can someone please point me to a simple example of how to do this in ASP.Net?
Generally speaking, the proxy runs on your web server - most likely IIS in your case - and 'relays' the requests to another server on a different domain.
Here's an example of one implemented in C# .NET
Fast, Streaming AJAX proxy
You may be able to avoid a proxy by using a technique like JSONP. Assuming the web service you're talking to supports JSONP (for example, Flickr or Twitter both offer a JSONP API) or you have control over the data the web service sends back, you can send JSON data between domains using a library that features JSONP.
For example, in jQuery, you can make a remote JSON call:
jQuery.getJSON("http://www.someothersite.com/webservice?callback=?", function(result)
{
doStuffWithResult(result);
});
Because the call is to another domain, jQuery automatically uses some trickery to make a cross domain call. jQuery will automatically replace the ? in the url with a callback function name that the web service can use to format the JSON data being returned.
If you're the one controlling the web service, you can handle the JSONP request by getting the request parameter called "callback" which will be set to the callback function name you need to use. The callback function takes one parameter, which is the JSON data you want to send back. So, if the callback parameter is set to "jsonp2342342", you'll want the web service to respond like this:
jsonp2342342({key: value, key2: value});
If the web service you're using already supports JSONP, you won't have to worry about doing the formatting yourself.
You can write a simple .NET page to retrieve the remote page and display it on your site:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Net;
using System.IO;
namespace Proxy
{
public partial class _Proxy : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string proxyURL = string.Empty;
try
{
proxyURL = HttpUtility.UrlDecode(Request.QueryString["u"].ToString());
}
catch { }
if (proxyURL != string.Empty)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(proxyURL);
request.Method = "GET";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode.ToString().ToLower() == "ok")
{
string contentType = response.ContentType;
Stream content = response.GetResponseStream();
StreamReader contentReader = new StreamReader(content);
Response.ContentType = contentType;
Response.Write(contentReader.ReadToEnd());
}
}
}
}
}
See my post about it: http://www.johnchapman.name/aspnet-proxy-page-cross-domain-requests-from-ajax-and-javascript/
No browsers allow cross-domain scripting, and although w3c has left space for this in its recommendation on the xmlHTTPRequest-object, we still have to wait for some time to see it implemented in a secure way ...
I'll give a pseudocode version for people seeking a general answer to the question.
SomeAjaxAbstraction.Request('proxyScript', {
parameters: {
address: 'http://somewhere.com/someapi?some=query'
}
});
Then in proxyScript:
var address = GET['address'];
if(ValidUrl(address) && ConnectionAllowed(address)) {
// Validating address and whitelisting services is an exercise to the reader
var response = SomeHttpGetFunction(address);
echo XssAndBadStuffFilter(response);
} else {
// Handle errors
}

Resources