Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Execute an Action in separate thread to unblock the UI

I have a form which is used to generate a Report. We are using RDLC reports and the Report is loaded in an aspx page.

So this is the code for the Form, form target is set to _blank, and opens in new Tab.

@using (Html.BeginForm("AssetReports", "AssetReports", FormMethod.Post, new { target = "_blank" }))
{
    <div class="row mt-15">
        <div class="col-md-12 text-center">
            <input type="submit" class="btn btn-primary" value="Show Report" />
        </div>
    </div>
}

This is the Controller action which redirects to the Report aspx page, where the Report is processed and displayed.

[HttpPost]
public void AssetReports(AssetReportsDTO model, AssetReportParametersDTO reportParameters)
{
    SessionHandler.AssetReport = model;
    SessionHandler.AssetReportParameters = reportParameters;

    switch (model.SelectedReportType)
    {
        case AssetReportTypesEnum.ExcessiveIdleReport:
            Response.Redirect("~/Reports/AssetReports/ExcessiveIdleReport/ExcessiveIdleReport.aspx");
            break;
    }
}

The Reports take 3,4 minutes to generate in some cases. and during this time the UI is blocked,

We want the report to generate on a separate thread so that user can use the UI while the report is generated.

Is there a way in MVC C# to Execute this Action in a separate Thread?

I have tried using the following, but the Context and Session are then NULL

Task.Factory.StartNew(() =>
{
    switch (model.SelectedReportType)
    {
        case AssetReportTypesEnum.ExcessiveIdleReport:
            Response.Redirect("~/Reports/AssetReports/ExcessiveIdleReport/ExcessiveIdleReport.aspx");
            break;
    }
});

and also:

new Thread(() =>
{
    switch (model.SelectedReportType)
    {
        case AssetReportTypesEnum.ExcessiveIdleReport:
            Response.Redirect("~/Reports/AssetReports/ExcessiveIdleReport/ExcessiveIdleReport.aspx");
            break;
    }
}).Start();

EDIT

Code to generate the Report in - This is the code that takes 3 to 4 minutes ExcessiveIdleReport.aspx

public partial class ExcessiveIdleReport1 : Page
    {
        private IReportsProvider _reportsProvider;

        protected void Page_Load(object sender, EventArgs e)
        {
            _reportsProvider = new ReportsProvider();
            if (!IsPostBack)
            {
                try
                {
                    var reportDetails = SessionHandler.AssetReport;
                    var reportParams = SessionHandler.AssetReportParameters;



                    var sPath = Server.MapPath("../ExcessiveIdleReport/ExcessiveIdleReport.rdlc");
                    var dsExcessiveReport =
                        _reportsProvider.GetExcessiveIdleReport(reportDetails.CompanyId, reportDetails.AssetId, reportDetails.StartDate,
                                                                reportDetails.EndDate, reportParams.SelectedIdleTime * 60);

                    ExcessiveIdleReportViewer.ProcessingMode = ProcessingMode.Local;
                    ExcessiveIdleReportViewer.LocalReport.EnableHyperlinks = true;
                    ExcessiveIdleReportViewer.HyperlinkTarget = "_blank";
                    ExcessiveIdleReportViewer.LocalReport.DataSources.Add(new ReportDataSource("ExcessiveIdleReport", dsExcessiveReport.Tables[0]));
                    ExcessiveIdleReportViewer.LocalReport.DataSources.Add(new ReportDataSource("ReportHeaderDetails", dsExcessiveReport.Tables[1]));
                    ExcessiveIdleReportViewer.LocalReport.DataSources.Add(new ReportDataSource("ReportSummary", dsExcessiveReport.Tables[2]));
                    ExcessiveIdleReportViewer.LocalReport.ReportPath = sPath;

                    ExcessiveIdleReportViewer.LocalReport.EnableExternalImages = true;
                    ExcessiveIdleReportViewer.LocalReport.SetParameters(param);

                    ExcessiveIdleReportViewer.LocalReport.Refresh();

                }
                catch (Exception ex)
                {
                    ErrorDiv.InnerText = string.Format("An error occured while generating the ExcessiveIdleReport, Please contact Support with following Message: [{0}] - [{1}]", ex.Message, ex.StackTrace);
                    ReportContentDiv.Visible = false;
                    ErrorDiv.Visible = true;
                }
            }
        }
    }

I have also tried using Ajax.BeginForm

 @using (Ajax.BeginForm("AssetReports", "AssetReports", new AjaxOptions() { HttpMethod = "POST", OnSuccess = "OpenReport"}, new { target = "_blank" }))
            {

            <div class="row mt-15">
                <div class="col-md-12 text-center">
                    <input type="submit" class="btn btn-primary" value="Show Report" />
                </div>
            </div>
}

JS:

function OpenReport(response) {
    var popup = window.open("about:blank", "_blank"); // the about:blank is to please Chrome, and _blank to please Firefox
    popup.location = '/TBReports/AssetReports/ExcessiveIdleReport/ExcessiveIdleReport.aspx';
}

I load all other Pages using Ajax:

Here is an image of the Asset Reports page 'Show Report' button which executes the action:

But once this button is clicked other UI Elements are Blocked. e.g. I can't load View with Group Reports until the Report has been generated.

enter image description here

like image 259
Dawood Awan Avatar asked Sep 27 '22 10:09

Dawood Awan


2 Answers

ASP.NET MVC tends to serialize requests for the same session unless you specify that session is readonly.

In this article by Microsoft, https://msdn.microsoft.com/en-us/library/ms178581.aspx, it states:

Concurrent Requests and Session State

Access to ASP.NET session state is exclusive per session, which means that if two different users make concurrent requests, access to each separate session is granted concurrently. However, if two concurrent requests are made for the same session (by using the same SessionID value), the first request gets exclusive access to the session information. The second request executes only after the first request is finished. (The second session can also get access if the exclusive lock on the information is freed because the first request exceeds the lock time-out.) If the EnableSessionState value in the @ Page directive is set to ReadOnly, a request for the read-only session information does not result in an exclusive lock on the session data. However, read-only requests for session data might still have to wait for a lock set by a read-write request for session data to clear.

You might want to look into making the session state readonly or use a more sensible technology like node.js. (just kidding about the last bit...)

See here for John Culviner's article: http://johnculviner.com/asp-net-concurrent-ajax-requests-and-session-state-blocking/, where he says:

A Solution for MVC 3

Luckily Microsoft has provided ENUM values in System.Web.SessionState.SessionStateBehavior that allow us to give up rights to an exclusive session lock. Mainly the values:

ReadOnly – Doesn’t block other requests because this request can’t update session Disabled – Can’t block, and your best option for performance in StateServer & SQL modes because no serialization needs to occur. Remember that all of session is serialized and de-serialized per user every time. Not just particular keys your are accessing.

Throw a new for MVC 3 attribute with your desired Enum value on your Controller class as such:

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
public class TestController : Controller
{
    public ActionResult Index()
    {
        System.Threading.Thread.Sleep(10000);
        return new EmptyResult();
    }
}
like image 187
goofballLogic Avatar answered Nov 15 '22 07:11

goofballLogic


Adding threads into your logic isn't the way to run something that doesn't 'lock' the UI. Each new request to your ASP.NET MVC application is automatically served on a new thread. If this wasn't the case then multiple people wouldn't be able to visit your site at the same time.

The key to your answer is AJAX. An AJAX request is something that kicks off a request in the background.

Explore replacing your @using (Html.BeginForm( with @using (Ajax.BeginForm( to make your request without reloading the page. You can also use $jQuery.ajax() to kick your request off from a button click for example.

jQuery.Ajax()

$(document).delegate('#myForm', 'submit', function(e) {

    e.preventDefault();

    $.ajax({
        type: 'POST',
        dataType: 'json',
        url: '/AssetReports',
        data: $('#myForm').serialize(), // Post data from form
        success: function (responseData) {

             // Perform redirect to report?
        },
        error: function (jqXHR, textStatus, errorThrown) {

             // Display error?
        }
    })
});

@using (Html.BeginForm("AssetReports", "AssetReports", FormMethod.Post, new { target = "_blank", id = "myForm" })) // Added id
{
    <div class="row mt-15">
        <div class="col-md-12 text-center">
            <input type="submit" class="btn btn-primary" value="Show Report" />
        </div>
    </div>
}

OR Ajax.BeginForm

 function OpenReport(data, status, xhr) {

     // Open the report in a new window
     window.open("\\link\to\report");
 }

 @using (Ajax.BeginForm("AssetReports", "AssetReports", null, new AjaxOptions { HttpMethod = "POST", OnSuccess = "OpenReport" }, new { target = "_blank" }))
 {
     // ... Form
 }
like image 44
Luke Avatar answered Nov 15 '22 06:11

Luke