3

I'm in the middle of my first foray into web development with MVC4, and I've run into a problem that I just know is one of the core issues in web development in general, but can't seem to wrap my mind around it.

So I have two models: Employee and Company. Employee has a Company, therefore /Employee/Create contains a field that is either a dropdown list for selecting an existing Company, or an ActionLink to /Company/Create if none exists. But here's the issue: If a user has to create a Company, I need to preserve the existing Employee model across both Views--over to /Company/Create, then back to /Employee/Create.

So my first approach was to pass the Employee model to /Company/Create with a @Html.ActionLink("Add...", "Create", "Organization", Model , null), but then when I get to Company's Create(Employee emp), all of emp's values are null.

The second problem is that if I try to come back to /Employee/Create, I'd be passing back an Employee, which would resolve to the POST Action.

Basically, the whole idea is to save the state of the /Employee/Create page, jump over to /Company/Create to create a Company record, then back to /Employee/Create to finish creating the Employee. I know there is a well understood solution to this because , but I can't seem to phase it well enough to get a good Google result out of it.

I've though about using Session, but it seems like doing so would be a bit of a kludge. I'd really like an elegant solution to this.

EDIT: It's hard to believe that there is not canonical solution to this problem. It feels like this should be the single most basic problem of working with HTTP.

Stephen Collins
  • 3,523
  • 8
  • 40
  • 61

6 Answers6

2

So it looks like your main issue is a lack of Data Source as Forty-Two said, or more generally some sort of persistence. This can be done by having a database as a data source to back your Model, which is likely what you'll want in a permanent solution. another option is to use a session. A session would be a quick solution if this is not production code and you need something to just "work". I would incline you to look into Entity Framework Code First approach (as it is relatively simple, and can create the database based off your current model).

You mentioned you are new to MVC, so I would recommend [Intro to ASP.NET MVC 4][1] it is a very easy to follow tutorial for beginners. I even started with that one.

EDIT

I would agree with it being odd to create a sort of "temp" entry in a DB that would later need to be cleaned up if the user abandons the page. One idea I have would be using ajax (generally I like to do this with the help of the jQuery library). I'm not sure what your knowledge is of library, but my basic premise would be something as follows...

  1. User clicks one of the buttons.
  2. By using an asynchronous call to your controller you do some work required, and return a result. Your controller method would likely need to return a JSONResult.
  3. Using this result, you update your view. My Idea would be use the results to populate a form.
  4. Now the user can hit "Submit" and be sent to a method which then Updates/Saves to the DB.

If you're unfamiliar with jQuery, you may want to check it out.

Post about using jQuery and JsonResult: https://stackoverflow.com/a/9463272/1200520

Community
  • 1
  • 1
Luke G
  • 367
  • 1
  • 4
  • 20
  • I *am* using EF Code First. But the problem is that it seems insane to me to create a record in the database before the user hits "Submit." If the user goes off to create a Company, but abandons it half way through, then now I have a useless half-finished record of an Employee in my database. – Stephen Collins Dec 07 '12 at 23:13
  • It looks like this would be the best approach. My plan now is to start learning JQuery and the AJAX approach to display a "Create Company" dialog within the Create Employee page. Thanks! – Stephen Collins Dec 14 '12 at 13:00
1

Did you know that <input type="submit" name"createEmployee" value="Create Employee" /> name attribute is passed within the POST request as parameter and the default ModelBinder will bind it?

This means, you can have multiple submit buttons on a single form.

Let's say you have a ViewModel like:

public class CreateCompanyWithEmployeeModel
{
public Employee Employee { get; set; }
public Company Company { get; set; }

public string createEmployee { get; set; }
public string createCompany { get; set; }

public string State { get { return (String.IsNullOrEmpty(createCompany)) ? "createEmployee" : "createCompany"; }}
}

And of course those custom types for your Employee and Company like:

public class Employee
{
   // Employee properties here
}

public class Company
{
   // Company properties here
}

Then you can have an action like:

    public ActionResult CreateCompanyWithEmployee(CreateCompanyWithEmployeeModel model)
    {
        switch (model.State)
        {
            case "createEmployee":
                // Do your createEmployee Stuff
                break;
            case "createCompany":
                // Do your createCompany Stuff
                break;
            default:
                throw new NotImplementedException(String.Format("State {0} not implemented", model.State));
        }
        return View();
    }

And in the view you'd have a form like:

<% using(Html.BeginForm()) { %>

    <%=(Model.State == "createEmployee") ? Html.EditorFor(Model.Employee) : Html.DisplayFor(Model.Employee) %>
    <%=(Model.State == "createCompany") ? Html.EditorFor(Model.Company) : Html.DisplayFor(Model.Company) %>

    <input type="submit" name="<%=Model.State%>" value="Submit" />

<% } %>

Then you just need to build your EditorFor and DisplayFor controls as required by your situation. If you don't want to show the Employee during the Company creation, then you just use HiddenFor to persist the Employee data into the form until the Company editing is completed. Same thing with not showing the Company Data before the Employee is completely valid.

I know this example isn't completely clean but for the sake of clarity of the approach, I needed to keep it this simple. Basically, if you use this approach a lot, you'd have the ViewModel inherit an interface that takes care of the viewmodel state handling and you'd have custom Html Helpers to render the forms depending on the state and so forth and so forth.

We use this approach a lot. And it works on every browser in the world.

Jani Hyytiäinen
  • 5,293
  • 36
  • 45
0

You can't pass objects around in the url (GET requests), that's why the values were all null in your first approach. Typically, you'd use the EmployeeId to pass as a parameter and then re-fetch the Employee object from the data source in your controller.

So your link would be

@Html.ActionLink("Add...", "Create", "Organization", new { id = Model.EmployeeId }, null)

And the Action Method

public ActionResult Create(int id)
{
  //hit the db to grab the Employee by id
}

This is the typical way to handle this scenario in asp.net mvc.

Forty-Two
  • 7,535
  • 2
  • 37
  • 54
  • But that's just it. At the point the user clicks the "Create Company" link, the `Employee` will not have been posted yet, so there's no `Id` to put in the URL. The whole idea is to save the state of the `/Employee/Create` page, jump over to `/Company/Create` to create a `Company` record, then back to `/Employee/Create` to finish creating the `Employee`. – Stephen Collins Nov 30 '12 at 01:12
0

The only way to pull this off is with some type of persistence strategy. By which method is up to you. I would suggest an InProc session if the volume is compliant.

There are a lot of excellent suggestions here.. Session variables in ASP.NET MVC

Here also is a very nice example of an abstract implementation.. http://blogs.planetcloud.co.uk/mygreatdiscovery/post/Abstracting-session-in-ASPNET-MVC.aspx

Community
  • 1
  • 1
NickSuperb
  • 1,174
  • 1
  • 8
  • 28
0

You could use TempData to store data until next request. http://www.devcurry.com/2012/05/what-is-aspnet-mvc-tempdata.html

You can also use POST request which is also quite good and common solution.

You may also consider adding a company in ajax modal and refresh dropdown values after.

  • Would probably work fine in single-server development environment but in case the production site is on a farm, it would stop working. Persisting the temp data on the view would carry the values between requests. – Jani Hyytiäinen Dec 13 '12 at 07:01
0

You have to make a workaround like this:

  1. Add a string property to the Company model, named EncodedEmployee
  2. Show your create employee view the way you are doing it now. The user might click the "create company" link.
  3. When the user selects "create company", then make sure an Action, named, for example: "CreateCompanyFirst" is invoked. This Action will look like this:

    public ActionResult CreateCompanyFirst(Employee emp)
    {
        string jsonemp = Json(emp);
    
        Company emptyCompany = new Company{};
    
        emptyCompany.EncodedEmployee =  jsonemp ;
    
        return View(emptyCompany);
    }
    
  4. When the "create company view" gets rendered, make sure to save the EncodedEmployee property in a hidden field.

  5. I'm guessing you have an action already implemented for creating a Company. Now this code need to be slightly modified:

    public ActionResult CreateCompany(Company cmp)
    {
        // Save your company
    
        // if cmp.EncodedEmployee  is not null
    
            // De serialize the employee, return View(employee)
    
        // else return what you were returning before
        // (maybe "thanks for creating a company, come back soon")
    }
    
  6. You will be back to your employee screen with the fields filled as before.

Obviously this means that the views/actions/routes were functional and were not expecting or doing any magic tweaks, otherwise it wont work.

Adrian Salazar
  • 5,279
  • 34
  • 51