質問

I have a create view and an edit view, almost identical. They both include a cascading dropdown list. The value of the second list is part of the data that's being created or edited. The first dropdown is just a filter for values in the second list.

Both views use the same script which attaches a handler for the change event in the first dropdown list. The handler makes a post to a method in the controller.

When I run the create view and change the first dropdown list it works fine. The second list is updated. When I try in the edit view I get a 500 Internal Server Error.

I've had a look in Firebug. I put a break in the handler. It gets called in both cases when selecting. The data that gets sent appears to be the same ( $(this).serialize() looks the same ).
The Firebug console says: The required anti-forgery form field "__RequestVerificationToken" is not present. I don't see that I'm requiring one to call this method, but there are of course other methods (actions) that do require one.

I've never used Firebug before yesterday though, so there's possibly a lot more that could be checked if one knew how. (I am completely new to MVC and web applications in general.)

I also have a break in my controller method. It gets hit when called from the create view only.

In a fit of desperation I tried changing the call type to GET instead of POST. That resulted in a controller method with a different name being called (the HttpGet EDIT), which I find disturbing to say the least.

I get the feeling that the method I'm calling isn't found (in edit). Instead an attempt is made with some other method (I don't know which one) and that method requires an antiforgery token.

EDIT : A nip of fresh air and I realize that of course I know which action gets called: It's the HttpPost Edit action (which requires an antiforgery token). This is of course the normal form submit action. A quick run in the debugger confirms this. The fact that this call fails for the reason at hand is clear. But why does this action get called at all?

UPDATE : The Firebug console indicates that different actions are being called. In the Create case it's .../_an_alphanumeric_literal_/Order/PopulateAvtalDDL, which is what I want. In the Edit case it's .../_an_alphanumeric_literal_/Order/Edit/PopulateAvtalDDL. Is this a routing problem? If so, I still don't get it. Both calls are made from the same script and I only have one route set up (basically the default route for MVC 4):

routes.MapRoute(
            name: "Default",
            url: "_an_alphanumeric_literal_/{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

UPDATE : I finally found some related information: Relative URLs in AJAX requests. It is a routing problem then. If I change my script URL from the relative PopulateAvtalDDL to the absolute /_an_alphanumeric_literal_/Order/PopulateAvtalDDL it works from both views. The answer in the quoted link states that a path is relative to the current URL in the browser. In my case that is http://localhost:51852/_an_alphanumeric_literal_/Order/Edit/6?markedOrderId=0 and http://localhost:51852/_an_alphanumeric_literal_/Order/Create?markedOrderId=0. When the relative URL is constructed the last segment is replaced. In the Create case that is Create, but in the Edit case it is 6 (ID of edited data). I understand what's happening!

My solution then:

Add a div around the filter DDL (in both views) setting the URL: <div id="kundDDL" data-url="@Url.Action("PopulateAvtalDDL", "Order")">. Then in the script access that URL url: $('#kundDDL').data('url').

Here's the code for the create view:

@model AssetMgmt.Models.OrderEditVM
@using AssetMgmt.Models;

@{
    ViewBag.Title = MsgString.LblCreateNew + " order";
}

<h2>@ViewBag.Title</h2>


@using (Html.BeginForm(null, null, FormMethod.Post))
{
    @Html.ValidationSummary(true)
    @Html.AntiForgeryToken()

    @Html.HiddenFor(m => m.MarkedOrderId)

    <fieldset>
        <legend>Filter</legend>

        <div class="editor-label">
            @Html.LabelFor(m => m.KundId)
        </div>
        <div class="dropdownlist">
            @Html.DropDownListFor(m => m.KundId, Model.KundDropDown, string.Empty)
        </div>
        <div class="dropdown-validation-error">
            @Html.ValidationMessageFor(model => model.KundId)
        </div>
    </fieldset>

    <fieldset>
        <legend>Orderdata</legend>

        <div id="avtalDDL">
            @Html.Partial("_AvtalDDL")
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Ordernummer)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Ordernummer)
            @Html.ValidationMessageFor(model => model.Ordernummer)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Beställare)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Beställare)
            @Html.ValidationMessageFor(model => model.Beställare)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Orderdatum)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Orderdatum)
            @Html.ValidationMessageFor(model => model.Orderdatum)
        </div>

        <p>
            <input type="submit" value="@MsgString.LblSave" />
        </p>
    </fieldset>
}
<div>
    @Html.ActionLink(MsgString.LblBack, "Index", new { markedOrderId = Model.MarkedOrderId })
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script type="text/javascript" src="~/Scripts/jquery.maskedinput-1.3.1.min.js"></script>
    <script type="text/javascript" src="~/Scripts/assetMgmtMasks.js"></script>
    <script src="~/Scripts/assetMgmtOrderPopulateDDL.js" type="text/javascript"></script>
}

Here's the Edit view:

@model AssetMgmt.Models.OrderEditVM
@using AssetMgmt.Models;

@{
    ViewBag.Title = MsgString.LblEdit + " order";
}

<h2>@ViewBag.Title</h2>

@using (Html.BeginForm(null, null, FormMethod.Post))
{
    @Html.ValidationSummary(true)
    @Html.AntiForgeryToken()

    @Html.HiddenFor(model => model.OrderId)
    @Html.HiddenFor(model => model.Timestamp)
    @Html.HiddenFor(model => model.MarkedOrderId)

    <fieldset>
        <legend>Filter</legend>

        <div class="editor-label">
            @Html.LabelFor(m => m.KundId)
        </div>
        <div class="dropdownlist">
            @Html.DropDownListFor(m => m.KundId, Model.KundDropDown, string.Empty)
        </div>
        <div class="dropdown-validation-error">
            @Html.ValidationMessageFor(model => model.KundId)
        </div>
    </fieldset>

    <fieldset>
        <legend>Orderdata</legend>

        <div id="avtalDDL">
            @Html.Partial("_AvtalDDL")
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Ordernummer)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Ordernummer)
            @Html.ValidationMessageFor(model => model.Ordernummer)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Beställare)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Beställare)
            @Html.ValidationMessageFor(model => model.Beställare)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Orderdatum)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Orderdatum)
            @Html.ValidationMessageFor(model => model.Orderdatum)
        </div>

        <p>
            <input type="submit" value="@MsgString.LblSave" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink(MsgString.LblBack, "Index", new { markedOrderId = Model.MarkedOrderId })
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script type="text/javascript" src="~/Scripts/jquery.maskedinput-1.3.1.min.js"></script>
    <script type="text/javascript" src="~/Scripts/assetMgmtMasks.js"></script>
    <script src="~/Scripts/assetMgmtOrderPopulateDDL.js" type="text/javascript"></script>
}

The script:

$(document).ready(function () {
    //Kund select
    $('#KundId').change(function () {
        $.ajax({
            url: 'PopulateAvtalDDL',
            type: 'post',
            data: $(this).serialize(),
            success: function (result) {
                $('#avtalDDL').html(result);
            },
            error: function (xhr, ajaxOptions, thrownError) {
                alert(xhr.status);
                alert(xhr.statusText);
                alert(thrownError);
            }
        });
        return false;
    });
});

The signature of the controller method:

[HttpPost]
public PartialViewResult PopulateAvtalDDL(int kundId = 0)
役に立ちましたか?

解決

I finally found some related information: Relative URLs in AJAX requests. It is a routing problem. If I change my script URL from the relative PopulateAvtalDDL to the absolute /_an_alphanumeric_literal_/Order/PopulateAvtalDDL it works from both views. The answer in the quoted link states that a path is relative to the current URL in the browser. In my case that is
http://localhost:51852/_an_alphanumeric_literal_/Order/Edit/6?markedOrderId=0
and
http://localhost:51852/_an_alphanumeric_literal_/Order/Create?markedOrderId=0. When the relative URL is constructed the last segment is replaced. In the Create case that is Create, but in the Edit case it is 6 (ID of edited data).

My solution then:

Add a div around the filter DDL (in both views) setting the URL:
<div id="kundDDL" data-url="@Url.Action("PopulateAvtalDDL", "Order")">.
Then in the script access that URL
url: $('#kundDDL').data('url').

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top