Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC 4 - Nested Models - How to set up proper associations?

I'm coming to asp.net MVC 4 from a background in Rails, and I am having some trouble figuring out how to deal with nested models.

I am using Entity Framework 4.3 with a CodeFirst approach.

The application is fairly simple and has a Set model - which has many Franchises. And it has a Franchise model - which belongs to Sets.

The POCO classes were created along with the appropriate navigation properties, then I scaffolded the controllers with the views, then generated the database.

Here are the classes:

Set

 namespace FranchiseManagerTest.Models
    {
        public class Set
        {        
            [Key]
            public int SetID { get; set; }
            public string SetName { get; set; }
            public string MainAddress { get; set; }
            public string TimeZone { get; set; }       
            public virtual ICollection<Franchise> Franchises { get; set; }
        }
    }

Franchise

namespace FranchiseManagerTest.Models
{
    public class Franchise
    {
        [Key]
        public int FranchiseID { get; set; }        
        public int SetID { get; set; }
        public string FranchiseName { get; set; }
        public string FranchiseNumber { get; set; }     

        public virtual Set Set { get; set; }

    }
}

Then I created some test Franchise Sets and Franchises.

Here is the main view:

enter image description here

The project requires that all Franchises related to a Set are visible within the corresponding Set. Also, I need to be able to add a Franchise to a Set while I am in the Set's details.

I created a screenshot to illustrate what it looks like after I click "details" on the main view:

enter image description here

The New Franchise button is created with an ActionLink that looks like this:

@Html.ActionLink("New Franchise", "Create", "Franchise", null,  new { @class = "btn btn-large btn-primary" })

This takes me to the Franchise Create page that looks like this:

enter image description here

The problem, as you can see, is that the dropdown that should contain the related Set Model is not pre-populated - instead I have to click the dropdown and re-select the Set that I am already in.

The dropdown is being populated - but by all existing Sets - instead of the Set that I am inside of.

Here is the dropdown when I click on it:

enter image description here

Inside of the Franchise Create View, this is what that dropdown and its label look like:

       <div class="editor-label">
            @Html.LabelFor(model => model.SetID, "Set")
        </div>
        <div class="editor-field">
            @Html.DropDownList("SetID", String.Empty)
            @Html.ValidationMessageFor(model => model.SetID)
        </div>

Here is what I have tried:


I attempted to pass in a SetID value to the View using the ActionLink:

@Html.ActionLink("New Franchise", "Create", "Franchise", Model.SetID,  new { @class = "btn btn-large btn-primary" })

I then tried to change the String.Empty attribute of the Create View to this:

 <div class="editor-label">
        @Html.LabelFor(model => model.SetID, "Set")
    </div>
    <div class="editor-field">
        @Html.DropDownList("SetID", Model.SetID)
        @Html.ValidationMessageFor(model => model.SetID)
    </div>

However, this did not work and generated an Invalid Argument error.

Here is a possible clue (or another issue?)

When I visit the Create Franchise View from the associated Set that I am in, here is what the URL looks like:

enter image description here

I would assume that if the associations between Set and Franchise were correct, and when I went to add a Franchise within a Set, that the URL would look like this:

localhost:52987/Set/1/Franchise/Create

Is this a wrong assumption to make - is this possible to do in .NET MVC?

Any advice here is greatly appreciated!


EDITS


Here are the routes in my Global.asax file if this is helpful:

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

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

As Requested - here are both the GET and POST Create methods of the Franchise Controller:

 //
    // GET: /Franchise/Create

    public ActionResult Create()
    {
        ViewBag.SetID = new SelectList(db.Sets, "SetID", "SetName");
        return View();
    }

    //
    // POST: /Franchise/Create

    [HttpPost]
    public ActionResult Create(Franchise franchise)
    {
        if (ModelState.IsValid)
        {
            db.Franchises.Add(franchise);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        ViewBag.SetID = new SelectList(db.Sets, "SetID", "SetName", franchise.SetID);
        return View(franchise);
    }

Just let me know if more code will be helpful.

like image 279
PhillipKregg Avatar asked Jun 21 '12 17:06

PhillipKregg


2 Answers

I'm a bit confused as to what you're looking to do, but I'd suggest a few changes:

I'd change this:

<div class="editor-label">
    @Html.LabelFor(model => model.SetID, "Set")
</div>
<div class="editor-field">
    @Html.DropDownList("SetID", Model.SetID)
    @Html.ValidationMessageFor(model => model.SetID)
</div>

to this:

<div class="editor-label">
    @Html.LabelFor(model => model.SetID, "Set")
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.SetID, (SelectList)ViewBag.FranchiseSets)
    @Html.ValidationMessageFor(model => model.SetID)
</div>

If you pass in the SetID value to your Create ActionMethod and assign it, it should get passed to the view. It looks like you're not populating the DropDownList, so there's no option in the select list to be selected.

public ActionResult Create(int SetID)
{
    ViewBag.FranchiseSets = new SelectList(...);
    var franchise = new Franchise { SetID = SetID };
    return View(franchise);
}
like image 165
justinb138 Avatar answered Nov 15 '22 21:11

justinb138


It's unclear exactly how your actions and routes are set up, but you may want to try this:

@Html.ActionLink("New Franchise", "Create", "Franchise", new{Model.SetID},  new { @class = "btn btn-large btn-primary" })

By creating an object with a SetID property, MVC will know that the parameter is supposed to be named "SetID".

PS--may I suggest renaming Set to (FranchiseSet, or FranchiseGroup, etc.), to avoid naming collisions? Set is one of the most ambiguous and common words in the English language.

like image 41
StriplingWarrior Avatar answered Nov 15 '22 21:11

StriplingWarrior