【问题标题】:Redirect from successful AJAX call in ASP.NET MVC从 ASP.NET MVC 中成功的 AJAX 调用重定向
【发布时间】:2013-11-27 14:09:05
【问题描述】:

我正在构建一个 Web 应用程序,它是我认识的老师的测试/问题/答案应用程序。其中一项要求是测试必须一次完成……即……不保存并继续。我选择通过使用 jQuery UI 的模式弹出窗口来实现这一点。在开始测试之前,我有一个用于测试的搜索页面和一个模态确认对话框。我遇到的问题是在用户选择继续测试后,它需要重定向到一个新页面,其中将显示第一个问题的模式弹出窗口(之后的每个问题都将通过 AJAX 加载到相同的模式弹出窗口中)。我可以进行搜索以及确认弹出窗口,但我无法弄清楚如何重定向到问题页面。我的 TestingController 中的 Question 操作被调用并且参数正确,但是一旦返回 View,ASP.NET 就会抛出一个异常,告诉我第一个参数为空。以下是控制此行为的 sn-ps:

来自 Search.cshtml(这个 div 是模态确认弹窗):

 <div id="dialog" title="Confirm Test">

        <p>
            <span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span>Are you sure you want to take the selected test?
        </p> 

    </div>

@section scripts
{
    <script type="text/javascript">

        $(document).ready(function () {

            $(".takeTest").click(function (e) {

                e.preventDefault();

                var testId = $(this).data("test-id");
                $("#selectedTestId").val(testId);

                $("#dialog").dialog("open");

            });

            $("#dialog").dialog({
                dialogClass: "no-close",
                autoOpen: false,
                modal: true,
                resizable: false,
                height: 180,
                buttons: {
                    'Confirm': function () {

                        var appId = $("#applicantId").val();
                        var id = $("#selectedTestId").val();

                        $.ajax({
                            url: "/Testing/TakeTest/",
                            data: { applicantId: appId, testId: id },
                            type: "POST",
                            success: function (results) {
                                window.location.href = "/Testing/Question/";
                            }
                        });
                    },
                    'Cancel': function () {
                        $(this).dialog('close');
                    }
                }
            });
        });

    </script>
}

来自 TestingController:

        [HttpPost]
        public ActionResult TakeTest(int applicantId, int testId)
        {
            try
            {
                ApplicantTest test = TestingRepository.GetInstance().TakeTest(applicantId, testId);
                return RedirectToAction("Question", "Testing", new { applicantId = applicantId, testId = testId });
            }
            catch
            {
                return RedirectToAction("Search", "Testing");
            }
        }


        [HttpGet]
        public ActionResult Question(int applicantId, int testId)
        {
            Question nextQuestion = TestingRepository.GetInstance().GetNextQuestion(applicantId, testId);
            NextQuestionViewModel model = AutoMapper.Mapper.Map<NextQuestionViewModel>(nextQuestion);
            model.ApplicantId = applicantId;
            model.QuestionNumber = (nextQuestion.ApplicantTestAnswers.Count + 1);
            return View(model);
        }

来自 Question.cshtml:

@Html.HiddenFor(x => x.ApplicantId, new { id = "applicantId" })
@Html.HiddenFor(x => x.TestId, new { id = "testId" })

<div id="dialog" title="Question: @Model.QuestionNumber">

    @Html.Partial("_NextQuestionPartial", Model)

</div>

@section scripts
{
    <script type="text/javascript">

        $(document).ready(function () {

            $("#dialog").dialog({
                dialogClass: "no-close",
                autoOpen: true,
                modal: true,
                resizable: false,
                height: 400,
                buttons: {
                    'Next': function () {


                    }
                }
            });


        });

    </script>
}

来自 _NextQuestionPartial.cshtml:

@Ajax.BeginForm("Question", "Testing", new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "nextQuestionContainer" })
{
    <fieldset>
        <legend>Question # @Model.QuestionNumber</legend>

        <div id="nextQuestionContainer">
            <p>Q:  @Model.QuestionText</p>
            <ul>
                @foreach (var answer in Model.QuestionAnswers)
                {
                    <li>
                        @answer.Answer
                    </li>
                }
            </ul>
        </div>
    </fieldset>
}

Search.cshtml 中的模态确认弹出良好,确认按钮调用 TestingController 中的 Question 操作(在 VS 中使用断点验证)。 applicationId 和 testId 都填充了正确的值。正确创建了 NextQuestionViewModel 并调用了返回 View(model),但这是我对正在发生的事情迷失的地方。我得到了异常:参数字典包含方法“System.Web.Mvc.ActionResult Question(Int32, Int32)”的不可空类型“System.Int32”的参数“applicantId”的空条目。

但是,如果我直接从 URL 将这些参数添加到查询字符串,页面将加载并且问题的模式弹出窗口会正确显示。这似乎是一个路由问题,但我是 MVC 的新手,我无法理解为什么 TestingController::TakeTest 中的 RedirectToAction 在我可以在 action 方法 Question. /p>

【问题讨论】:

  • 等等。当您从头到尾遵循该过程时,是否会在用户在行 'return RedirectToAction("Question", "Testing", ...' 还是第一个问题成功显示并然后抛出异常?
  • 它发生在第一个问题显示之前。使用 Fiddler,我发现有 2 次调用 action 方法。第一个来自我的 TakeTest 操作方法,然后是第二个(从 ajax 成功函数中的 window.location.href 行猜测)。第一个具有查询字符串参数,而第二个没有...因此出现错误。我不确定进行此重定向的最佳方式,所以也许这就是我一开始就构建它的方式。非常感谢任何想法。
  • 如果我删除 window.location.href 对话框不会关闭,但只会执行第一个请求(使用查询字符串参数),但对话框保持打开状态不会发生重定向。如果我将 $(this).dialog('close') 添加到成功函数,我会收到一个 javascript 错误:“JavaScript 运行时错误:在初始化之前无法调用对话框上的方法;尝试调用方法'close'”不确定如何关闭模式对话框,然后将其重定向到问题页面。
  • 对不起...另一个更新:$(this) 是错误的,因为我在成功函数中...更改为 $("#dialog") 确实关闭了对话框,但重定向仍然没有发生...尽管 Fiddler 仍在使用适当的参数显示对测试/问题的调用。
  • 这里的问题是您的 MVC 操作正在返回一个重定向到您的 success 函数,但您的 success 函数没有处理它。此外,window.location.href 不提供任何查询字符串参数。我会继续发布答案。

标签: jquery asp.net-mvc modal-dialog


【解决方案1】:

最快的解决方法是改变你的线路:

window.location.href = "/Testing/Question/";

到以下:

window.location.href = "/Testing/Question?applicantId=" + appId + "&testId=" + id;

编辑:

用户用户确认他/她想参加考试后,您可以:

  1. 重定向页面并替换其全部内容。
  2. 用测试的第一个问题替换页面的一部分。

根据您的问题,我假设您选择#1。这样,在用户确认后,浏览器将被重定向到类似于http://YourSite.com/Testing/Question?applicantId=123&testId=456"的URL,而不是停留在"http://YourSite.com/TestPage"。

我能想到的最好方法是使用一个简单的 HTML 表单,完全绕过 $.ajax 调用。看起来您使用 jQuery 引用了两个元素,它们的 ID 为“applicantId”和“selectedTestId”。我不确定您如何为这些元素分配值,但您可以将它们包装成如下形式:

<form id="testForm" action="/Testing/TakeTest" method="post">
    <!-- Put your two elements here -->
    <!-- For example: -->
    <input id="applicantId" name="applicantId" type="hidden" />
    <input id="selectedTestId" name="testId" type="hidden" />
</form>

注意:注意上述输入的name 属性如何对应于Question 操作方法中的参数名称。 然后,您可以将整个 $.ajax 调用替换为以下内容:

$('#testForm').submit();

默认情况下,这会将您的数据提交给您的TestPage POST 操作,这反过来会将您的浏览器重定向到“/Testing/Question...”并呈现新页面。

总结:由于您不想简单地替换页面的一部分,因此请避免使用 ajax 调用,只需使用表单提交数据。

【讨论】:

  • 谢谢威廉。这确实有效,如果我从 TakeTest 中删除 RedirectToAction,则只进行一次调用,这也很好。无论如何我都会接受你的回答,但我确实对你的回答有疑问。当我看到“最快的……”时,这让我相信可能有更好的方法来实现这一目标。在我的情况下,最好从服务器重定向并以某种方式在成功函数中处理它,或者像你在答案中那样构建重定向?只是想确保我正在学习最好的方法,因为我没有时间紧缩......感谢你的时间。
  • 你打赌!当用户在单击“确认”后同意参加测试时,这实际上取决于您想要发生的事情。我将编辑我的答案以涵盖这一点。
  • 谢谢...我认为完成要求的最佳方法是使用模式对话框。我有另一个要求,可以多次进行测试,但在 3 个月内只能进行一次。因此,确认用于确保他们想要开始测试。我试图做的是在选择测试时从 Search.csthml 中弹出对话框(即...进行测试?链接)。我认为最好重定向到另一个页面(Question.cshtml),然后显示一个带有问题的新模式对话框。回答问题后,对话框将通过另一个 AJAX 调用获取新问题
  • 抱歉...房间用完了。您当前的答案很好,我只是好奇是否有更好的设计。我是一个试图转换的 WinForms 人,所以只想确保我“正确”地做事。 :)
  • 我刚刚更新了我的答案。如果有任何不清楚的地方,或者如果您想让我扩展任何内容,请随时提问!
猜你喜欢
  • 2019-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-18
  • 1970-01-01
  • 2015-01-15
  • 1970-01-01
  • 2014-10-25
相关资源
最近更新 更多