Streaming text output for long-running action? - asp.net-mvc

I have a few utility actions that return text output via return Content("my text","text/plain").
Sometimes these methods take a few minutes to run (i.e. log parsing, database maintenance).
I would like to modify my action method so that instead of returning all of the output at once, the text is instead streamed to the client when it is ready.
Here's a contrived example:
public ActionResult SlowText()
{
var sb = new System.Text.StringBuilder();
sb.AppendLine("This happens quickly...");
sb.AppendLine("Starting a slow 10 second process...");
System.Threading.Thread.Sleep(10000);
sb.AppendLine("All done with 10 second process!");
return Content(sb.ToString(), "text/plain");
}
As written, this action will return three lines of text after 10 seconds. What I want is a way to keep the response stream open, and return the first two lines immediately, and then the third line after 10 seconds.
I remember doing this 10+ years ago in Classic ASP 3.0 using the Response object. Is there an official, MVC-friendly way to accomplish this?
--
Update: using Razor .cshtml in the app; but not using any views (just ContentResult) for these actions.

Writing directly to the Response object should work, but only in some simple cases. Many MVC features depend on output writer substitution (e.g. partial views, Razor view engine, and others) and if you write directly to the Response your result will be out of order.
However, if you don't use a view and instead write straight in the controller then you should be fine (assuming your action is not being called as a child action).

I would skip the MVC controller entirely since you are going to break encapsulation anyway. In it's place I'd use a barenaked IHttpHandler implementation, streaming directly to the aforementioned output stream.

You are exposing yourself to a browser timeout if the process takes longer than originally intended. Then you don't have a way to recover what happened / unless you implement a separate method that gives the information on the long running process.
Given that you want the other method anyway, you can start a long running process and return immediately. Have the browser check the other method that gives the latest information on the long running process. On the last time I had to do this, I kept it simple and just set the refresh header from the controller before returning the view.
As for starting a long running process, you can do something like this:
// in the controller class
delegate void MyLongProcess();
//...
// in the method that starts the action
MyLongProcess processTask = new MyLongProcess(_someInstance.TheLongRunningImplementation);
processTask.BeginInvoke(new AsyncCallback(EndMyLongProcess), processTask);
//...
public void EndMyLongProcess(IAsyncResult result)
{
try{
MyLongProcess processTask = (MyLongProcess)result.AsyncState;
processTask.EndInvoke(result);
// anything you needed at the end of the process
} catch(Exception ex) {
// an error happened, make sure to log this
// as it won't hit the global.asax error handler
}
}
As for where do you put the log of the actions that happened, it's up to you to how long lived you want it to be. It can be as simple as a static field/class where you add the info of the ongoing process, or instead saving it to a data store where it can survive an application recycle.
The above assume this is all about a long running process that goes on reporting the actions that has been done. Streaming is a different subject, but the above might still play a role in keeping the operations in your controller & only the piece responsible of streaming what becomes available to the client in the action result.

You can implement your custom ActionResult like ContentStreamingResult and use HttpContext, HttpRequest and HttpResponse in the ExecuteResult method.
public class ContentStreamingResult : ActionResult
{
private readonly TextReader _reader;
public ContentStreamingResult(TextReader reader)
{
_reader = reader;
}
public override void ExecuteResult(ControllerContext context)
{
var httpContext = context.HttpContext;
//Read text from the reader and write to the response
}
}
public class YourController : Controller
{
public ContentStreamingResult DownloadText()
{
string text = "text text text";
return new ContentStreamingResult(new System.IO.StringReader(text));
}
}

Try Response.Flush and BufferOutput to false. Note it would work with the different action results, you have to directly write into the response object. Probably you can use it with conjunction with AsyncController.

Related

Run a long running job using the fire and forget strategy with Thymeleaf in Reactor and r2dbc

I am trying to achieve a fire and forget type of effect with webflux, thymeleaf and r2dbc. I have two endpoints, one to add an employee and another to list all employees. I want to simulate a slow database access so I have a thread sleep of several seconds before I call the DB.
Now, the effect I expect to see when I call /add is that my controller returns immediately and the page add is rendered at once. However, I'm not sure how to achieve this. With the current code nap() happens before I can return a Mono. In other words, I'm trying to run a long running job in the background without blocking the controller.
I have the following model:
#Data
public class Employee {
#Id
private Long id;
private String name;
}
The annotated controller has following methods:
#GetMapping(value = "/")
public String home(Model model) {
model.addAttribute("employees", repo.findAll());
return "home";
}
#GetMapping(value = "/add")
public Mono<String> add() {
return Mono
.defer(this::getEmployee)
.doOnNext(e -> repo.save(e).subscribe())
.thenReturn("add");
}
private Mono<Employee> getEmployee() {
final var e = new Employee();
e.setName("John");
nap(); // calls thread sleep for a few sec
return Mono.just(e);
}
My question is how can I wrap the long running job but at the same time preserve a Controller based notation (instead of functional) and also render the add page immediately? I am aware of some similar questions like this and this, but I don't seem to be able to achieve the behaviour I need.
Edit:
lkatiforis' suggestion and this SO question were a push in the right direction. I had to adjust their example a bit because the employee didn't persist. The change is in add():
public String add() {
Mono.just(employee)
.delayElement(Duration.ofSeconds(5))
.doOnNext(e -> repo.save(e).subscribe())
.subscribe();
return "add";
}
employee is just an instance of Employee with a populated name. The delayElement operator pauses for 5 seconds without blocking. Finally, I had to call subscribe() on repo.save() and at the end in order for it to work. I assume that if subscribe() is only called on doOnNext() then the main chain that starts with Mono.just() is never executed.
I guess nap() method executes Thread.sleep or something similar, right? Thread.sleep is blocking the main thread making the application unresponsive. You can use delayElements operator to simulate a long-running operation:
private Mono<Employee> getEmployee() {
final var e = new Employee();
e.setName("John");
return Mono.just(e).delayElement(Duration.ofSeconds(5));
}

Amazon SWF queries

Over the last couple of years, I have done a fair amount of work on Amazon SWF, but the following points are still unclear to me and I am not able to find any straight forward answers on any forums yet.
These are pretty basic requirements I suppose, sure others might have come across too. Would be great if someone can clarify these.
Is there a simple way to return a workflow execution result (maybe just something as simple as boolean) back to workflow starter?
Is there a way to catch Activity timeout exception, so that we can do run customised actions in such scenarios?
Why doesn't WorkflowExecutionHistory contains Activities, why just Events?
Why there is no simple way of restarting a workflow from the point it failed?
I am considering to use SWF for more business processes at my workplace, but these limitations/doubts are holding me back!
FINAL WORKING SOLUTION
public class ReturnResultActivityImpl implements ReturnResultActivity {
SettableFuture future;
public ReturnResultActivityImpl() {
}
public ReturnResultActivityImpl(SettableFuture future) {
this.future = future;
}
public void returnResult(WorkflowResult workflowResult) {
System.out.print("Marking future as Completed");
future.set(workflowResult);
}
}
public class WorkflowResult {
public WorkflowResult(boolean s, String n) {
this.success = s;
this.note = n;
}
private boolean success;
private String note;
}
public class WorkflowStarter {
#Autowired
ReturnResultActivityClient returnResultActivityClient;
#Autowired
DummyWorkflowClientExternalFactory dummyWorkflowClientExternalFactory;
#Autowired
AmazonSimpleWorkflowClient swfClient;
String domain = "test-domain;
boolean isRegister = true;
int days = 7;
int terminationTimeoutSeconds = 5000;
int threadPollCount = 2;
int taskExecutorThreadCount = 4;
public String testWorkflow() throws Exception {
SettableFuture<WorkflowResult> workflowResultFuture = SettableFuture.create();
String taskListName = "testTaskList-" + RandomStringUtils.randomAlphabetic(8);
ReturnResultActivity activity = new ReturnResultActivityImpl(workflowResultFuture);
SpringActivityWorker activityWorker = buildReturnResultActivityWorker(taskListName, Arrays.asList(activity));
DummyWorkflowClientExternalFactory factory = new DummyWorkflowClientExternalFactoryImpl(swfClient, domain);
factory.getClient().doSomething(taskListName)
WorkflowResult result = workflowResultSettableFuture.get(20, TimeUnit.SECONDS);
return "Call result note - " + result.getNote();
}
public SpringActivityWorker buildReturnResultActivityWorker(String taskListName, List activityImplementations)
throws Exception {
return setupActivityWorker(swfClient, domain, taskListName, isRegister, days, activityImplementations,
terminationTimeoutSeconds, threadPollCount, taskExecutorThreadCount);
}
}
public class Workflow {
#Autowired
private DummyActivityClient dummyActivityClient;
#Autowired
private ReturnResultActivityClient returnResultActivityClient;
#Override
public void doSomething(final String resultActivityTaskListName) {
Promise<Void> activityPromise = dummyActivityClient.dummyActivity();
returnResult(resultActivityTaskListName, activityPromise);
}
#Asynchronous
private void returnResult(final String taskListname, Promise waitFor) {
ActivitySchedulingOptions schedulingOptions = new ActivitySchedulingOptions();
schedulingOptions.setTaskList(taskListname);
WorkflowResult result = new WorkflowResult(true,"All successful");
returnResultActivityClient.returnResult(result, schedulingOptions);
}
}
The standard pattern is to host a special activity in the workflow starter process that is used to deliver the result. Use a process specific task list to make sure that it is routed to a correct instance of the starter. Here are the steps to implement it:
Define an activity to receive the result. For example "returnResultActivity". Make this activity implementation to complete the Future passed to its constructor upon execution.
When the workflow is started it receives "resultActivityTaskList" as an input argument. At the end the workflow calls this activity with a workflow result. The activity is scheduled on the passed task list.
The workflow starter creates an ActivityWorker and an instance of a Future. Then it creates an instance of "returnResultActivity" with that future as a constructor parameter.
Then it registers the activity instance with the activity worker and configures it to poll on a randomly generated task list name. Then it calls "start workflow execution" passing the generated task list name as an input argument.
Then it wait on the Future to complete. The future.get() is going to return the workflow result.
Yes, if you are using the AWS Flow Framework a timeout exception is thrown when activity is timed out. If you are not using the Flow framework than you are making your life 100 times harder. BTW the workflow timeout is thrown into a parent workflow as a timeout exception as well. It is not possible to catch a workflow timeout exception from within the timing out instance itself. In this case it is recommended to not rely on workflow timeout, but just create a timer that would fire and notify workflow logic that some business event has timed out.
Because a single activity execution has multiple events associated to it. It should be pretty easy to write code that converts history to whatever representation of activities you like. Such code would just match the events that relate to each activities. Each event always has a reference to the related events, so it is easy to roll them up into higher level representation.
Unfortunately there is no easy answer to this one. Ideally SWF would support restarting workflow by copying its history up to the failure point. But it is not supported. I personally believe that workflow should be written in a way that it never fails but always deals with failures without failing. Obviously it doesn't work in case of failures due to unexpected conditions. In this case writing workflow in a way that it can be restarted from the beginning is the simplest approach.

Calling a WCF asynchronously from an ASP.NET MVC application

I have a very established n-tiered ASP.NET MVC application that currently does everything synchronously; all controllers inherit from Controller rather than AsyncController.
What I want to do is to take one of the lower tiers (that literally every flow consumes) and put in an awaited call to a WCF. Why await? Well, I figure that it will reduce the overall thread-count and make the server more scalable.
However, I am not in a position to change any of the code outside of this tier. So normally, one would change the calling methods from returning T to return 'async Task', and give the method name an "async" suffix, e.g.:
from
public string GetData()
to
public Task<string> GetDataAsync()
Okay, so I went about it with the following (cut down) code:
public ResponseObject GetSomething(RequestObject request)
{
return MyMethodAsync(request).Result;
}
private static async Task<ResponseObject> MyMethodAsync(RequestObject request)
{
using (var client = new Client(new NetTcpBinding(), new EndpointAddress(Url))))
{
await client.DoSomething(request).ConfigureAwait(true);
}
}
When I run this code (F5), it gets to the line where it calls the "await client.DoSomething(request).ConfigureAwait(true);" and then, unfortunately, that's the last I see of my thread. Occassionally it does return when I'm debugging (F11), but 99% of the time it doesn't.
The non-ASYNC method works perfectly fine
Debugging the WCF service shows it receives the request, processes it and returns
Using a console app, this works perfectly. It's only when I try this in one of my "middle" tiers of my web application that the thread disappears.
I've tried with .ConfigureAwait(true) and .ConfigureAwait(false).
Short answer: don't block on async code (your problem's the .Result which blocks, while its Task is a concurrent one, not a parallel one)
Long answer (and really good reading, too): Don't Block on Async Code

Long running process that will return a file

I'm using ASP.NET MVC and have a long running process. Specifically I am generating a large PDF for the user to download.
I understand the basic concept:
Action method gets called
New thread started to generate process
Return a View that tells the user the (pdf) is being generated
Use AJAX to call the server and ask for progress
Once finished, present the file to the user for download.
The parts I don't fully understand are:
The management of the thread across separate AJAX calls. I will possibly need some way of finding the running thread and requesting a status. Is there a static context I can keep a reference to the thread in? I'm aware of the Data Caching in HttpContext.Application, would that be suitable for this?
And how to present the completed file. Do I create a temp file and present a download link? Or can I make a final AJAX call that returns the file?
It works!
Here's what I've done:
Step 1 & 2 - Action Method gets called, long running thread is started
When my action method gets called, it generates a unique ID. I then instantiate an instance of my PdfGenerator class, create a new thread that calls PdfGenerator.Generate and start it.
public class PdfGenerator
{
public string State;
public byte[] Data;
public void Generate()
{
// Generate PDF/Long running process
// Should update State as it goes
// ...
// Once finished, Data is populated with the binary byte[]
}
}
Once the thread has started (or before starting) the generator instance is stored in the cache:
HttpContext.Cache[guid] = generator;
I also attach the guid to the ViewData so that it can be reference in my view script.
Step 3 & 4 - Display and update status/progress view
Now that the thread is running and PDF generation has begun, I can display my progress view script. Using jQuery's $.getJSON I am able to poll a separate Action to find the status of the generation:
[OutputCache(Duration = 0, VaryByName = "none", NoStore = true)]
public JsonResult CheckPdfGenerationStatus(string guid)
{
// Get the generator from cache
var generator = HttpContext.Cache[guid] as PdfGenerator;
if (generator == null)
return Json(null);
else
return Json(generator.State);
}
My view script interprets the Json and displays the appropriate progress information.
Step 5 - Present file to user
Once the generation is completed, the generators state is set accordingly and when jQuery receives this information, it can either make available a link, or directly send the file using javascripts location.href.
The Action method that sets up and returns the file simply gets the generator out of the cache and returns the attached byte[]
public ContentResult DownloadPdf(string guid)
{
var generator = HttpContext.Cache[guid] as PdfGenerator;
if (generator == null)
return Content("Error");
if (generator.State == "Completed")
{
return Content(generator.Data);
}
else
{
return Content("Not finished yet");
}
}
My my actual work I've got more detailed state such as Initialised, Running and Completed. As well as a progress percentage (expressed as a decimal, 1.0 being complete).
So yeah, hope that helps anyone else trying to do something similar.
The Cashe is very well suitable for that. Only one thing is to make sure the item cached is never removed while the process is running (You can use ItemPriority.NotRemovable for that).
You can save the file on disk in a temp folder or you can keep it in cache for some time (it depends).
I personally don' like to pollute hard disk with files so I would keep the file in the cache (with MediumPriority for a couple of minutes). But if the file is large and can be generated often consider using a Database of file system instead.
On the client, when the last Ajax request returns result( can look like {progress: "100%", resultUrl: "http://your.url/Where/ToGet/TheFile.aspx?file=GUID-OR-CACHE-KEY"} ) you can redirect the browser to a URL provided.
It, in turn, will render that file as a binary result.
Client redirect can be done using Javascript like this:
location.href = response.resultUrl;
BTW, how do you generate PDF? NFOP?

Struts2 Interceptor *after* JSP is rendered - how?

I was wondering if I can capture the result of an action after the result returns and the JSP is rendered. I want to be able to take the entire result (generated HTML) and push it into memcached so I can bring it via Nginx with-out hitting the application server. Any ideas?
PS: I know I can run the interceptor after the action executes but before the result returns and the JSP is rendered, but not after the JSP is rendered.
I haven't found a way to do this inside of struts2, your best bet it to create a servlet Filter and have it modify the OutputStream.
http://onjava.com/pub/a/onjava/2003/11/19/filters.html
Hey I know its quite late now to answer you might have already found out the answer, however for others to benefit I am posting the answer.
One thing that is very similar to what you are doing is done by sitemesh filter.
Yes, filter comes before and after the Struts2 filter itself, so you can mess with the inputs and outputs easily.
But struts does evaluate JSP/freemarker/velocity and generate the final html which is passed to the user. JSP is a bit trickey because internally a servlet is called but check out org.apache.struts2.views.freemarker.FreemarkerResult class, you can see the actual html getting generated in template.process(model, writer);. This writer is actually ServletActionContext.getResponse().getWriter();
Now to get the html all you need to do is
ServletActionContext.getResponse().getWriter().toString() //This does not work out of box. To get the toString() to work you need to use a ResponseWrapper - which is the same method to get result html in Filters. See- Programming Customized Requests and Responses.
Listing to modify resulting html in struts 2. This is not tested, but it is extracted from my code I have written earlier for custom template engine. I will probably post full description in Custom template engine for struts2
public class DecoratorInterceptor implements Interceptor {
public String intercept(ActionInvocation invocation) throws Exception {
final ActionContext context = invocation.getInvocationContext ();
HttpServletResponse responseParent = (HttpServletResponse)
context.get(ServletActionContext.HTTP_RESPONSE);
CharResponseWrapper wrapper = new CharResponseWrapper(responseParent);
ServletActionContext.setResponse(wrapper);
//Actual Action called
String result = invocation.invoke();
String htmlReturned = wrapper.toString();
//play with htmlReturned ...
String modifiedhtml = pushintoMemCache(htmlReturned );
CharArrayWriter car = new CharArrayWriter();
car.write(modifiedhtml );
PrintWriter out = responseParent.getWriter();
out.write(car.toString());
out.flush();
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
#Override
public void init() {
// TODO Auto-generated method stub
}
}
Read this article - http://struts.apache.org/2.0.6/docs/interceptors.html
SUMMARY:When you request a resource
that maps to an "action", the
framework invokes the Action object.
But, before the Action is executed,
the invocation can be intercepted by
another object. After the Action
executes, the invocation could be
intercepted again. Unsurprisingly, we
call these objects "Interceptors."
Question: How do you determine if the view has been generated? Do you set a request header or an some sort of a flag to determine if the view has been generated?
You could try throwing a MemCachedException to indicate that it is time to load into a mem cache. Your interceptor code could read
try {
return invocation.invoke();
} catch (MemCachedException mce) {
// Your code to upload to MemCache.
} finally {
// blah blah clean up.
}
Within your interceptor's intercept() method, the ActionInvocation parameter has a getResult() method which returns null before Action execution (i. e. before you call invocation.invoke() in your intercept() method) and contains an implementation of Result afterwards. That object should give you some way to access the data you need, but how this is done probably depends on the class that is actually used.
See also my somewhat related question and the answer I posted after figuring it out.

Resources