iasksillyquestions
iasksillyquestions

Reputation: 5689

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

Whats the best practice approach to creating a form that is used to both create new models and edit existing models?

Are there any tutorials that people can point me in the direction of?

Upvotes: 46

Views: 45478

Answers (10)

Craig Stuntz
Craig Stuntz

Reputation: 126547

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.

Upvotes: 9

Dmitry Sikorsky
Dmitry Sikorsky

Reputation: 1501

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!

Upvotes: 9

Chtioui Malek
Chtioui Malek

Reputation: 11515

this is not always the best practice because it depends on the case, here's how i did it

1/ i combined the controller actions for create and edit

public PartialViewResult Creedit(string id = null)
{
    if (id == null)
    {
        // Create new record (this is the view in Create mode)
        return PartialView();
    }
    else
    {
        // Edit record (view in Edit mode)
        Client x = db.ClientSet.Find(id);
        if (x == null) { return PartialView("_error"); }
        // ...
        return PartialView(x);
    }
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Creedit(Client x)
{
    if (x.id == null)
    {
        // insert new record
    }
    else
    {
        // update record
    }
}

2/ i combined the edit and create views into one view i call Creedit

// if you need to display something unique to a create view
// just check if the Model is null
@if(Model==null){
}

so i have 1 view and 2 actions (1 post and 1 get) instead of 2 views and 4 action.

Upvotes: 1

ChrisFox
ChrisFox

Reputation: 346

I use something like

[HttpGet]
public ActionResult EntityEdit(Guid id)
{
    return View();
}

and

[HttpGet]
public ActionResult EntityCreate()
{
   return View("EntityEdit");
}

That seems to work OK.

Upvotes: 0

Mariano Desanze
Mariano Desanze

Reputation: 8153

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.

Upvotes: 37

Serge Wautier
Serge Wautier

Reputation: 21878

If the entity has some kind of internal private key (e.g. an "id" member that is always > 0), you can use /Edit/0 instead of /Create

Upvotes: 0

Trevor de Koekkoek
Trevor de Koekkoek

Reputation: 2506

I have a system that I think works pretty well. In my shared views I have 2 generic forms, Edit.aspx and New.aspx

Then in my specific view folder I have a control named EditItems.ascx

In my edit form I have the form tags and specific buttons for edit and in the new form I have the form tags and specific buttons for new. In each I have Html.RenderPartial("EditItems.ascx")

This way your user control can be strongly typed and yet you are reusing the look and feel of the edit and new pages.

Now in some cases, your new page might have a different layout than the Edit page. In that case just add "Edit.aspx" to your specific view folder.

I find this gives me the best combination of reuse while still allowing full customization should I need it. And as for controller actions, yes they should be separate actions.

Upvotes: 0

Perpetualcoder
Perpetualcoder

Reputation: 13571

It could be (should be IMO) one controller but different controller actions. Also make sure you have proper HTTP verbs associated with appropriate action. Follow the tutorial posted by E Rolnicki and you will be on your way!

Happy Coding!!

Upvotes: 1

Keith Williams
Keith Williams

Reputation: 2357

I put the form itself in a user control - say, Views/Shared/WidgetForm.ascx. I put all form fields in this user control, but NOT the form tags themselves.

The views, say Views/Widgets/New.aspx and Views/Widgets/Edit.aspx, have the form tags in them and all the "surroundings" - instructions for filling in the form, page title, etc etc. Then they include the user control inside the form tags.

The user control simply takes a Widget object, and displays a form based on the results. Putting sensible defaults in new Widget options therefore becomes important, but you're doing that anyway, right? ;)

Upvotes: 0

E Rolnicki
E Rolnicki

Reputation: 1717

Scott Gu will show the way

Upvotes: 11

Related Questions