2

I am a beginner on MVC and I am trying to Create a web app where I want the user to be able to create new items and view them in the same page. I am trying to do this by using partial view for the Create Action inside the Index view.

The problem is I am not able to redirect from the child to the Index to update the list after creating new item. And I'm getting this error(Child actions are not allowed to perform redirect actions.)

Here is my model

 public class Expense
{
    public int Id { get; set; }

    [Required]
    public string Title { get; set; }

    [Required]
    [DataType(DataType.Currency)]
    public decimal Amount { get; set; }

    //[Required]
    public string ApplicationUserId { get; set; }
}

,here are my Index and Create Actions

    public ActionResult Index()
    {
        return View(db.Expenses.ToList());
    }
    public ActionResult Create()
    {
        return PartialView("Create", new Expense());
        //return View();
    }

    // POST: /Expense/Create
    // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
    // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include="Id,Title,Amount,ApplicationUserId")] Expense expense)
    {
        if (ModelState.IsValid)
        {
            expense.ApplicationUserId = User.Identity.GetUserId();
            db.Expenses.Add(expense);
            db.SaveChanges();
            return RedirectToAction("Index"); // Here is where The exception is thrown
        }
        return View();
    }

And here is my Index view

@model IEnumerable<HomeManager.Models.Expense>
@{
    ViewBag.Title = "Index";
}
<h2>Index</h2>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Title)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Amount)
        </th>
        <th></th>
    </tr>
    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Amount)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
                @Html.ActionLink("Details", "Details", new { id = item.Id }) |
                @Html.ActionLink("Delete", "Delete", new { id = item.Id })
            </td>
        </tr>
    }
</table>
<p>
    @Html.ActionLink("Create New", "Create")
    @{Html.RenderAction("Create", "Expense");}
</p>

And here is my Create view

@model HomeManager.Models.Expense

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>


@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Expense</h4>
        <hr />
        @Html.ValidationSummary(true)

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Amount, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Amount)
                @Html.ValidationMessageFor(model => model.Amount)
            </div>
        </div>

        @*<div class="form-group">
            @Html.LabelFor(model => model.ApplicationUserId, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ApplicationUserId)
                @Html.ValidationMessageFor(model => model.ApplicationUserId)
            </div>
        </div>*@

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default"/>
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}
Baso
  • 1,354
  • 4
  • 19
  • 41
  • You need to show the `Create.cshtml` view as well. –  Apr 25 '16 at 21:59
  • @StephenMuecke I did. – Baso Apr 25 '16 at 22:07
  • Cant spot the issue, but [this question/answer](http://stackoverflow.com/questions/25015833/child-actions-are-not-allowed-to-perform-redirect-actions-after-setting-the-sit) explains the issue. –  Apr 25 '16 at 22:23
  • I get the exception from the Create Action. exactly this line (return RedirectToAction("Index");) – Baso Apr 25 '16 at 23:17
  • Have you tried with just Response.Redirect("/Index"); ? Substitute Index with your route. – Mario Lopez Apr 25 '16 at 23:33
  • OK, Got it now. You need to change `@{ Html.RenderAction("Create", "Expense"); }` to `@{ Html.RenderPartial("Create", "Expense", new Expense()); }` (note you do not need the `Create()` GET method) –  Apr 25 '16 at 23:36
  • @StephenMuecke There is no RenderPartial takes these arguments. I also Tried RenderPartial("Create", new HomeManager.Models.Expense) it shows the Create form but it doesn't post. – Baso Apr 26 '16 at 00:06
  • Yes its `RenderPartial("Create", new Expense())` (lazy cut and paste). You will need to specify the action and controller in the form - `@using (Html.BeginForm("Create", "Expense")) { ....` –  Apr 26 '16 at 00:12
  • @MarioLopez I tried Response.Redirect("Expense/Index"); but I get this exception (Server cannot append header after HTTP headers have been sent.) – Baso Apr 26 '16 at 00:17
  • @StephenMuecke I got confused now. If you explain more I would be extremely grateful. – Baso Apr 26 '16 at 00:21

2 Answers2

3

The error is occurring because you html for the form is generating

<form action="/Expense/Index" method="post">

not action="/Expense/Create" as it needs to be. If you put a breakpoint on both the Index() GET and Create(Expense expense) POST methods, you will see that your hitting both of them when you submit the form.

To solve this, explicitly set the action and controller names in the BeginForm() method of the partial view

@using (Html.BeginForm("Create", "Expense"))

Note that since your Create() GET method is not performing any logic, you can also use

@{ Html.RenderPartial("Create", new Expense()); }

in lieu of RenderAction

-1

If you are trying to display the new records immediately they are created then I suggest you use a Javascript library like Knockout, Angular Js or Ember.

Alternatively if you want to use only MVC then you will need to make an ajax call to a method that returns a partial view of the updated Expense record and place this partial view in your DOM in your view.

plasteezy
  • 257
  • 6
  • 14
  • I am sure there is a way to do it without using third-parties, otherwise asp.net-MVC wouldn't be such a great framework. – Baso Apr 25 '16 at 22:09