1

I'm trying to learn ASP.Net Core, and I'm having trouble figuring out how to move data around in the MVC 'style'(?). The ASP.NET Core tutorial on Microsoft's website that I'm following doesn't deal with any relations between models. (seems to be an stupid oversight on their part?) Maybe my Google-Fu is off, so if this is obvious, or anyone has some reading material I can look at for ASPNETCore, I'd appreciate it.

I've got two Models. One called Device and another called DeviceType. In my Device Model, I've got a reference to a DeviceType attribute.

public class Device{
    [Key]
    public int ID {get; set;}

    [Required]
    [Display(Name = "Device Name")]
    public String deviceName {get; set;}

    [Required]
    [Display(Name="Description")]
    public String deviceDescription {get; set;}

    [Required]
    [Display(Name="Type")]
    public DeviceType deviceType{get; set;}
}

public class DeviceType{
    [Key]
    public int ID {get; set;}
    [Required]
    [Display(Name="Type")]
    public String name {get;set;}
    [Required]
    [Display(Name="Description")]
    public String description {get;set;}

}

I want to figure out how to - for each action (CRUD) - reference and get the deviceType for the particular Device object. I also don't understand how to reference the related Device Type when I work on my Views.

GET Create * How do I get a SelectList of my DeviceTypes so that a user can create a Device and choose what type of Device it is? * I've done some research on the View portion, and it looks like I need to use the shorthand Razor "@model.SelectList()" syntax, but the parameters are confusing for me. I don't understand exactly what it's asking for. Here's what the controller looks like right now for that action.

public IActionResult Create()
    {

        return View();
    }

Index * In the loop showing all devices, how would I also include the name of the deviceType in the resultant table of devices? * In the view, I would assume I could use @Html.LabelFor to show what the data's name is, but how would I get the actual values?

public async Task<IActionResult> Index()
    {
        return View(await _context.Device.ToListAsync());
    }

Update/Edit * How would I retrieve the Device Objects' DeviceType, and then allow the user to edit it?

// GET: Devices/Edit/5
    public async Task<IActionResult> Edit(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var device = await _context.Device.SingleOrDefaultAsync(m => m.ID == id);
        if (device == null)
        {
            return NotFound();
        }
        return View(device);
    }

Delete * I haven't looked at this yet, but I'm putting it here in case theres any caveats when programming other actions...

// GET: Devices/Delete/5
    public async Task<IActionResult> Delete(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var device = await _context.Device
            .SingleOrDefaultAsync(m => m.ID == id);
        if (device == null)
        {
            return NotFound();
        }

        return View(device);
    }

Again, if there's some kind of resource I can look at other than Microsoft's "build an asp.net core application with Mac and Linux with Visual Studio Code", that would explain how to reference relations in models inside of views/controllers, that would be great.

Thank you

Julian
  • 33,915
  • 22
  • 119
  • 174

3 Answers3

3

First off, I'm assuming Entity Framework is being used here, as that's the most common setup by far.

Getting deviceType

With EF, relationships can be loaded lazily, eagerly, or explicitly. Lazy-loading is the autopilot method: the data is just magically there when you need. However, in order to enable lazy-loading, the property must be virtual. This is because EF must override the property to add the lazy-loading logic to it, and it cannot do that if the property is not virtual.

public virtual DeviceType deviceType{get; set;}

Alternatively, you can eager or explicit-load your relationship. Explicit loading pretty much has all the cons of lazy-loading with none of the pros, so it's not really recommended, unless you're doing it for a very specific reason. Still, you can achieve that via:

context.Entry(device).Reference(m => m.deviceType).Load();

Finally, you can eagerly load the relationship, but this must be done along with the initial query (hence, the "eager").

var devices = context.Devices.Include(m => m.deviceType);

Without doing one of these, your relationship property will be null.

Creating a drop down list for device types

In your view, you'll fairly obviously want to use Html.DropDownListFor. However, you have a problem in that you don't have a property on Device to bind to. The posted value of a select will be a simple type, like an integer id, so you can't bind it directly to a property like deviceType. You have two options:

  1. Add a property to Device for the foreign key, rather than relying on the implicit one EF creates:

    public int deviceTypeId { get; set; }
    
  2. Create a view model with a property like the above. On post, you'll map the values from your view model onto your entity, and when it comes to deviceTypeId, you'll use that to pull the appropriate device type from the database and then set deviceType on your entity with that.

Next, you'll need an IEnumerable. Don't bother creating an actualSelectList` instance. Razor can take care of that, and things generally work much more smoothly when you just let Razor handle it. Creating that is fairly straight-forward:

ViewBag.DeviceTypeOptions = context.DeviceTypes.Select(m => new SelectListItem
{
    Value = m.Id.ToString(),
    Text = m.Name
});

Then, in your view:

@Html.DropDownListFor(m => m.deviceTypeId, (IEnumerable<SelectListItem>)ViewBag.DeviceTypeOptions)

If you do use a view model, rather than your entity directly, it's better to put the options on your view model instead of ViewBag, but you can get the job done either way.

Getting deviceType name

LabelFor is for generating an actual HTML label. Assuming DeviceType has a property Name the result would be:

<label for="Name">Name</label>

Internally, LabelFor will call DisplayNameFor, which is how you get the property's actual name (or the value from the Display attribute). The result of that would just be:

Name

What you're looking for is DisplayFor or simply just the @ syntax. The following are functionally equivalent and would both output something like Some Awesome Device Type:

  1. @Html.DisplayFor(m => m.Name)
  2. @Model.Name

Editing deviceType

This one's a little too broad for Stack Overflow. Simply, you'll just render fields for it in your form, like anything else. You'll just need to pay attention to the naming of your inputs, as you'll need to maintain the object hierarchy. For example, if you're editing a Device, you'd have inputs for deviceType like:

<input type="text" name="deviceType.Name" />

However, as long as you use the *For style helpers, that works itself out for the most part.

Deleting

There's not really anything special to consider here, as EF's default is to cascade. If for some reason you've decided (or it was decided for you in an existing database) to not cascade, then you'll simply need to delete the device types before you delete the device, i.e. manually do the same thing the cascade would.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Lazy loading is not yet possible with EF Core. https://learn.microsoft.com/en-us/ef/core/querying/related-data – Alexan May 05 '17 at 18:55
  • @Alex: Yeah, I forgot about that. Or, maybe I just figured they had it sorted after almost two years ;). No big deal. Honestly, I haven't see many good reasons to lazy load, anyways. Generally, you're just going to eager load everything you need. – Chris Pratt May 05 '17 at 18:59
  • Even if EF supports it, don't use lazy loading in web apps if you can help it. Here's why: http://ardalis.com/avoid-lazy-loading-entities-in-asp-net-applications – ssmith May 06 '17 at 03:36
  • Oh my goodness. Thank you so much for all your information and help. you broke it down like I did in the question, and made everything straight forward. Thank you everyone else for contributing as well :) – Pilapodapostache May 06 '17 at 18:34
1

To include the device types when getting the list of devices, assuming this is using Entity Framework, use Include like _context.Device.Include(d => d.DeviceType).ToListAsync(). This will... well, include the associated entities you specify. There's a lot more to Entity Framework so I suggest looking up additional info on it when dealing with the models and context.

As for the list of types that you can select from, you first need to get the list of available device types separately, then pass it to the view (in the viewbag or however else you choose) and then... I haven't worked with MVC Core much but I suspect it's pretty similar to older versions but anyway something like MVC6 Dropdownlist of Countries

Community
  • 1
  • 1
Alex Paven
  • 5,539
  • 2
  • 21
  • 35
0

First of all you need to include foreign key in your Device model:

public int DeviceTypeID {get; set;}

And then you should include this DeviceType property how it answered here:

_context.Device.Include(d => d.DeviceType).ToListAsync()
Community
  • 1
  • 1
Alexan
  • 8,165
  • 14
  • 74
  • 101