How to redirect a ASP.NET Core MVC partial view after file download

2020-05-20 asp.net asp.net-mvc asp.net-core-mvc partial-views

I have an asp.net core MVC partial view called ExportPagePartial that allows user to export a page from the system and downloads it. In the HttpGet controller action I show the partial view (as a Modal pop-up) to get user input.

Modal Popup

<a class="dropdown-item" asp-action="ExportPagePartial" asp-route-userId="@Model.UserId" asp-route-businessAccountId="@Model.BusinessAccountId" asp-route-projectId="@Model.ProjectId" asp-route-pageId="@Model.PageId" data-toggle="modal" data-target="#ModalPlaceholder" title="Export page."><i class="fas fa-cloud-download-alt"></i> &nbsp; Export</a>

Controller Get Action

[HttpGet]
public IActionResult ExportPagePartial(string userId, string businessAccountId, string projectId, string pageId)
{       
    ExportPageViewModel model = new ExportPageViewModel()
    {
       // Set properties
    };

    return PartialView(nameof(ExportPagePartial), model);
}

Once the user hits Export button from the Modal pop-up partial view (which is a form submit action) the following HTTPPost action is correctly called. In this action I have to get the file from the Web Api and then download it via the browser, however after download is complete i want to close the partial view. Once the download is complete the partial view is still visible.

The return action never works and partial modal pop-up view does not close return RedirectToAction(nameof(BlahRedirectAction));

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ExportPagePartial(ExportPageViewModel model)
 {
            // Call Web API to get the file              
            string downloadUrl = "blah_blah_url";
            using (HttpResponseMessage httpResponse = await WebApiClient.HttpClient.PostAsJsonAsync(downloadUrl, unprotectedExportInput))
                {
                    if (!httpResponse.IsSuccessStatusCode)
                    {
                        throw new InvalidOperationException(await httpResponse.Content.ReadAsStringAsync());
                    }

                    // Download the file now.
                    ActionContext actionContext = new ActionContext(HttpContext, ControllerContext.RouteData, ControllerContext.ActionDescriptor, ModelState);
                    FileStreamResult fileContent = File(await httpResponse.Content.ReadAsStreamAsync(), httpResponse.Content.Headers.ContentType.MediaType, httpResponse.Content.Headers.ContentDisposition.FileName);
                    await fileContent.ExecuteResultAsync(actionContext);
                }

            // Redirect to main pain
            // The view never redirects and partial view is still visible
            return RedirectToAction(nameof(BlahRedirectAction));
 }

Answers

fileContent.ExecuteResultAsync(actionContext);

This is because when you download the file, ExportPagePartial has determined the return flow, and will not perform the RedirectToAction.

I suggest that you change the post method that triggers ExportPagePartial to ajax to achieve, so that you can successfully execute ExportPagePartial and after the method, redirect the page to what you want in js.

Here is a complete code of my demo based on your code:

  public class ExportTestController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
        [HttpGet]
        public IActionResult ExportPagePartial(string userId, string businessAccountId, string projectId, string pageId)
        {
            ExportPageViewModel model = new ExportPageViewModel()
            {
                Id = 1,
                Gender = "male",
                Name = "aaa",
                Number = "1231244"
            };
            return PartialView(nameof(ExportPagePartial), model);
        }
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> ExportPagePartial(ExportPageViewModel model)
        {
            // Call Web API to get the file              
            string downloadUrl = "blah_blah_url";

              using (HttpResponseMessage httpResponse = await WebApiClient.HttpClient.PostAsJsonAsync(downloadUrl, unprotectedExportInput))
                {
                    if (!httpResponse.IsSuccessStatusCode)
                    {
                        throw new InvalidOperationException(await httpResponse.Content.ReadAsStringAsync());
                    }

                    // Download the file now.
                    ActionContext actionContext = new ActionContext(HttpContext, ControllerContext.RouteData, ControllerContext.ActionDescriptor, ModelState);
                    FileStreamResult fileContent = File(await httpResponse.Content.ReadAsStreamAsync(), httpResponse.Content.Headers.ContentType.MediaType, httpResponse.Content.Headers.ContentDisposition.FileName);
                    await fileContent.ExecuteResultAsync(actionContext);
                }

            // Redirect to main pain
            // The view never redirects and partial view is still visible
            return RedirectToAction(nameof(BlahRedirectAction));
        }

Index.cshtml:

@{
    ViewData["Title"] = "Index";
    Layout = null;
}

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script>
    $(function () {
        $("a").click(function () {
            var route = $(this).attr("href");
            $('#partial').load(route);
        })

        $("form").submit(function () {
            $.ajax({
                url: $("form").attr('action'),
                type: 'Post',
                data: $("form").serializeArray(),
                success: function () {
                    //$("#ModalPlaceholder").hide();
                    window.location.href = "/ExportTest/BlahRedirectAction";
                }
            });
        })
    })

</script>
<a class="dropdown-item" asp-action="ExportPagePartial"
   asp-route-userId="1" asp-route-businessAccountId="1"
   asp-route-projectId="1" asp-route-pageId="1"
   data-toggle="modal" data-target="#ModalPlaceholder" title="Export page."><i class="fas fa-cloud-download-alt"></i> &nbsp; Export</a>

<div class="modal fade" id="ModalPlaceholder" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
    <form asp-action="ExportPagePartial" method="post">
        <div id="partial">
        </div>
    </form>
</div>

ExportPagePartial.cshtml:

@model ExportPageViewModel
<div class="modal-dialog" role="document">
    <div class="modal-content">
        <div class="modal-header">
            <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
        <div class="modal-body">
            <div class="form-group">
                <label asp-for="Id" class="control-label">@Model.Id</label>
                <input asp-for="Id" class="form-control" hidden />
            </div>
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label>
                <input asp-for="Name" class="form-control" />
                <span asp-validation-for="Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Gender" class="control-label"></label>
                <input asp-for="Gender" class="form-control" />
                <span asp-validation-for="Gender" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Number" class="control-label"></label>
                <input asp-for="Number" class="form-control" />
                <span asp-validation-for="Number" class="text-danger"></span>
            </div>
        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
            <button type="submit" class="btn btn-primary" >Save changes</button>
        </div>
    </div>
</div> 

Here is the test result:

enter image description here

Related