ASP.NET MVC - using the same form to both create and edit

Do not use the same controller action. New = HTTP POST; edit = HTTP PUT, so that's two different things. Both actions can and should be on the same controller, though.

I like the idea of using a user control for common features (e.g., editors), and wrapping that in action-specific views for stuff which should only appear on new or edit, but not both.


NerdDinner will really show the way.

Create.aspx

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<NerdDinner.Models.Dinner>" MasterPageFile="~/Views/Shared/Site.Master"  %>
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
    Host a Nerd Dinner
</asp:Content>
<asp:Content ID="Create" ContentPlaceHolderID="MainContent" runat="server">
    <h2>Host a Dinner</h2>
    <% Html.RenderPartial("DinnerForm"); %>
</asp:Content>

Edit.aspx

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<NerdDinner.Models.Dinner>"
    MasterPageFile="~/Views/Shared/Site.Master" %>
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
    Edit: <%:Model.Title %>
</asp:Content>
<asp:Content ID="Edit" ContentPlaceHolderID="MainContent" runat="server">
    <h2>Edit Dinner</h2>
    <% Html.RenderPartial("DinnerForm"); %>
</asp:Content>

DinnerForm.ascx

<%@ Language="C#" Inherits="System.Web.Mvc.ViewUserControl<NerdDinner.Models.Dinner>" %>
<script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>
<% Html.EnableClientValidation(); %>
<%: Html.ValidationSummary("Please correct the errors and try again.") %>
   <% using (Html.BeginForm())
      { %>
   <fieldset>
       <div id="dinnerDiv">
           <%:Html.EditorForModel() %>
           <p>
               <input type="submit" value="Save" />
           </p>
       </div>
       <div id="mapDiv">
           <%: Html.EditorFor(m => m.Location) %>
       </div>
   </fieldset>
   <% } %>

Take into account that this form is using Html.EditorForModel(), which is an innovative method for generating all the fields at once, and you have to study its disadvantages before using it. But you can easily take the rest of the example to separate your common form from the create and edit views.

Finally you can view the controller code here if you are interested.


Scott Gu will show the way


Assumptions

  1. This is good for the user to see different URLs for different actions in the browser. For example '/pages/create' and '/pages/edit/1'.

  2. This is good for developer to have only one action+view pair both to create and edit pages because they are usually very similar. (Also, this is good to have one controller per entity.)

Solution

Default routes registration is '{controller}/{action}/{id}' We can add two more rules before this one:

{controller}/create (should point to 'CreateOrEdit' action)

{controller}/edit/{id} (should point to 'CreateOrEdit' action too)

We can have now something like this:

public static void RegisterRoutes(RouteCollection routes)
{
  routes.MapRoute(
    name: "Create",
    url: "{controller}/create",
    defaults: new { controller = "Default", action = "CreateOrEdit" }
  );

  routes.MapRoute(
    name: "Edit",
    url: "{controller}/edit/{id}",
    defaults: new { controller = "Default", action = "CreateOrEdit" }
  );

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

So now both create and edit requests will be handled by 'CreateOrEdit' action. Others will go the default way.

Next what we should do is to add 'CreateOrEdit' action for HttpGet and HttpPost in our controller:

[HttpGet]
public ActionResult CreateOrEdit(int? id)
{
  return this.View(new CreateOrEditViewModelBuilder(this).Build(id));
}

[HttpPost]
public ActionResult CreateOrEdit(CreateOrEditViewModel сreateOrEditViewModel)
{
  if (this.ModelState.IsValid)
  {
    Page page = new CreateOrEditViewModelMapper(this).Map(сreateOrEditViewModel);

    if (сreateOrEditViewModel.Id == null)
      this.UnitOfWork.GetRepository<IPageRepository>().Create(page);

    else this.UnitOfWork.GetRepository<IPageRepository>().Edit(page);

    this.UnitOfWork.Save();
    return this.RedirectToAction("Index");
  }

  return this.View(сreateOrEditViewModel);
}

And the last we have to add view named 'CreateOrEdit'. We can user 'this.Model.Id == null' there to know whether we create or edit.

Result

Now we don't have duplicate code and can have obvious urls like this:

/pages (to see all pages)

/pages/create (to create new page)

/pages/edit/1 (to edit existing page)

/pages/delete/1 (to delete existing page)

I hope it will help someone!