-1

The following code works fine for rendering a Razor View to a string:

/// 
/// url: /api/createHtml
///
public ActionResult CreateHtml()
{
    // Heavy calculations
    MyModel myModel = new MyModel();
    myModel.Feature1 = ...;
    myModel.Feature2 = ...;
    myModel.Feature3 = ...;

    ViewData.Model = myModel;

    using (var stringWriter = new StringWriter())
    {
        var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, "MyView");
        var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, stringWriter);
        viewResult.View.Render(viewContext, stringWriter);
        viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);

        string html = stringWriter.GetStringBuilder().ToString();

        byte[] htmlBytes = Encoding.ASCII.GetBytes(html);

        System.IO.File.WriteAllBytes(Server.MapPath("~/temp/foo.html"), htmlBytes);
    }

    return JSON(new 
    {
         error = false,
         message = "Your view is available at temp/foo.html"
    });
}

The above code runs synchronously, meaning that an AJAX request to /api/createHtml/ will finish with the temp/foo.html file created.

I want to do this asynchronously: meaning that the AJAX request returns fast to the user with a message like: "Your view WILL BE available at temp/foo.html". And then the user must go check if the file is ready (by simply polling to the temp directory [or using other method, not important in this question])

So, when I try the same code within a Task, it doesn't work:

/// 
/// url: /api/createHtml
///
public ActionResult CreateHtml()
{
    new Task(() =>
    {
        // Heavy calculations
        MyModel myModel = new MyModel();
        myModel.Feature1 = ...;
        myModel.Feature2 = ...;
        myModel.Feature3 = ...;

        ViewData.Model = myModel;

        using (var stringWriter = new StringWriter())
        {
            var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, "MyView");
            var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, stringWriter);
            viewResult.View.Render(viewContext, stringWriter); // <--- Problem
            viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);

            string html = stringWriter.GetStringBuilder().ToString();

            byte[] htmlBytes = Encoding.ASCII.GetBytes(html);

            System.IO.File.WriteAllBytes(Server.MapPath("~/temp/foo.html"), htmlBytes);
        }

    }).Start();

    return JSON(new 
    {
         error = false,
         message = "Your view _WILL BE_ available at temp/foo.html"
    });
}

It doesn't work because it throws an Exception at viewResult.View.Render(...)

Value does not fall within the expected range.

It seems that the viewContext passed to viewResult.View.Render(...) is no longer valid in the new thread, as shown here: ASP.NET MVC: Exception rendering view in new thread

Is there a workaround for rendering a view within a Task ?

I know I could use "RazorEngine", a free library that renders razor views without all the controller mumbo jumbo, but I'd prefer to use native code, for reutilization of the code.

POST EDITED:

The few answers thought that I wanted to use "await async". I don't. I don't want to wait for the task to finish.

sports
  • 7,851
  • 14
  • 72
  • 129
  • 1
    Have you tried to do `Task.Run(() => { your code })` instead? This should just schedule the task on the thread pool. You could also use the TaskCreationOptions.LongRunning option if it's a long time task (in this case it won't use the pool) – Simon Mourier Mar 12 '19 at 07:52
  • I will try. But is it any different than `.Start()` ? – sports Mar 12 '19 at 18:26
  • Sure: https://stackoverflow.com/questions/29693362/regarding-usage-of-task-start-task-run-and-task-factory-startnew – Simon Mourier Mar 12 '19 at 23:17
  • You state the debugger stops without a reason. Have you looked at your application output? Have you tried enabling the settings in VS to capture all exceptions? – Jamie Rees Mar 18 '19 at 15:00
  • Try adding `try-catch` block inside `Task` constructor argument and see if any exception is thrown. – Leonid Vasilev Mar 18 '19 at 15:49
  • So I added the `try-catch` block inside `Task`, I updated the post with this information. – sports Mar 18 '19 at 19:31

2 Answers2

0

Well your tasks is probably doing what it suppose to do, but the main threat is not waiting for him.

Try using:

var myTask=new Task(() =>
{
    // all of your code

}).Start();

myTask.Wait();

check more info

The best way is using async and await, because Tasks should be used for asynchronous operations, good luck.

german1311
  • 95
  • 5
  • No, the task is supposed to finish creating a .html file, which is not doing. I tried this in sync mode and it worked fine (it indeed created the file) – sports Mar 05 '19 at 19:47
0

Asynchronous action methods, works async only inside the web server. The advantage of async methods is that they release the precious thread back to the thread pool, when they don't need it (as opposed to blocking it)... But the HttpRequest is still synchronous... your browser would synchronously wait for a response from an async action method.

What you need is to spawn a new thread or task to do your long running task and return from the action method.

Take a look at: Long Running Background Tasks in Asp.Net MVC3

Also this question might help: Do asynchronous operations in ASP.NET MVC use a thread from ThreadPool on .NET 4

Hooman Bahreini
  • 14,480
  • 11
  • 70
  • 137
  • But I want the code to run in parallel and actually return null to the user request. I don't want to await it. – sports Mar 06 '19 at 14:41
  • Maybe my question was not clear enough. I will edit it a little bit. I dont want the browser to synchronously wait the ajax response. I want the ajax response just to return "hey, the task is already running, come back later to check your view" – sports Mar 11 '19 at 20:48