9

I'm using EF code first and MVC5 C# for my web application. I've created a db table that contains master definitions for a couple dozen settings that are used to specify input and output parameters throughout the application.

I've also created a table that keys on user ID. Each user that gets created is given default settings that can be changed at any time using a User Preferences (razor) View page.

Now I am trying to code the controller actions for this View page. Most all of these user preferences will require selection from a dropdown list, but this is where it's confusing for me.

// UOM Master Table
public class UOMMaster
{
    public int Id { get; set; }
    public string MeasureId { get; set; }         // Text used as labels
    public double SortOrder { get; set; }         // User preferences sort order
    public string UOMId { get; set; }             // UOM abbreviation
    public bool IsUserPreference { get; set; }    // true if is a stored user preference
}

The master table data above may look like:

1,   "AbsolutePress",   1,   "psi",    true
2,   "AbsolutePress",   1,   "kPaa",   true
3,   "FluidFlow",       2,   "GPM",    true
4,   "FluidFlow",       2,   "m^3/Hr", true
5,   "FluidFlow",       2,   "l/s",    true

etc, etc...

The custom Identity with an ICollection containing :

public class ApplicationUser : IdentityUser
{
    // Default Units of Measure
    public virtual ICollection<AspNetUserUOMDefault> UOMDefaults { get; set; }
}

The preferred user defaults are stored in a table keyed on Identity's UserId:

public class AspNetUserUOMDefault
{
    [Key]
    public virtual ApplicationUser UserProfile { get; set; }

    public int Id { get; set; }
    public string MeasureId { get; set; }
    public string PreferredUomId { get; set; }
}

Using the tables above, I need to create a razor View that displays dropdown lists to display the user's preferred setting:

                  +--------------+---+
AbsolutePress:    | psi          | V |     <-- Dropdown list containing 
                  +--------------+---+         all options available for
                  | psi              |         AbsolutePress as defined in
                  | kPaa             |         master table, with user's
                  +------------------+         preferred setting selected.

FluidFlow:        +--------------+---+
                  | m^3/Hr       | V |     <-- Dropdown list containing
                  +--------------+---+         all options available for
                  | GPM              |         FluidFlow as defined in
                  | m^3/Hr           |         master table, with user's
                  | l/s              |         preferred setting selected.
                  +------------------+

etc, etc...

The GET Controller Action confuses me. I just don't have an idea on how to form the Model returned to the razor View, but it needs to be ordered by SortOrder (ascending) and contain an IList/ICollection of SelectList (perhaps??) containing the following (I'm not sure how to do this):

UOMPreferences
    MeasureId
    optionsList
        UomId
        UomId (Selected)
        UomId
UOMPreferences
    MeasureId
    optionsList
        UomId (Selected)
        UomId

   etc, etc..

The View template needs to display the contents of the Model using dropdown lists in SortOrder order. I envision this being dynamic so that a preference could be added to the database table without any changes required to either the Controller Actions or razor View template.

In the View there would be a foreach (I'm not sure how to do this):

@foreach (var preference in Model.UOMPreferences)
{
<div class="form-group">
    @Html.LabelFor(preference.MeasureId, new { @class = "col-md-3 control-label" })
    <div class="col-md-4">
        @Html.DropDownListFor(
            preference.MeasureId,
            new SelectList(preference.optionsList, "Value", "Text"), 
            new { @class = "form-control" })
    </div>
</div>
}

Does anyone have the experience and time to provide me with code snippet suggestions on how I can accomplish this? Probably my biggest confusion is in the Controller Actions themselves (GET and POST). I don't really care if the Model is created with Lambda or Linq, but I just can't envision how this can be done - and I'm sure it can be. My database models can likely be improved, though I think they will work?

rwkiii
  • 5,716
  • 18
  • 65
  • 114
  • I'm not sure I fully follow the question, but I find MVC drop downs are best approached by having static properties in the relevant model that contain all the options for the drop down, then use the DropDownListFor Html helper to construct it. – dyson Jul 20 '14 at 00:03
  • I'm confused by the structure of your data. Is the intention that each user be able to select a preference for `AbsolutePress` and a preference for `FluidFlow`, and that these values are used for formatting the display of other data in the application? –  Jul 20 '14 at 01:44
  • @barrik, the possible options for the dropdowns are contained in the UOMMaster table. Each user has their preferred setting stored in `ICollection UOMDefaults`. – rwkiii Jul 20 '14 at 02:12
  • @Stephen Muecke, yes. I think you understand. Since the application is used by users in many different countries around the world they may prefer Metric over US Standards for UOM. The application displays available products depending on performance/capacities specified by the user. I think you understand my intentions based on your comment. – rwkiii Jul 20 '14 at 02:16
  • In that case I think your database structure is not correct, or at least not ideal. I will post an answer shortly with the way I would approach this. –  Jul 20 '14 at 02:55
  • @Stephen Muecke, it'll be interesting to see your solution for this. I thought my approach would work, but I've never done anything like this so I wasn't sure. Thank you for taking the time! – rwkiii Jul 20 '14 at 03:14

2 Answers2

1

Based on you additional comments, I suggest a different structure.

Firstly, the database tables

AbsolutePressOptions: ID(PK), Name, IsDefault(bit)
FluidFlowOptions: ID(PK), Name(varchar), IsDefault(bit)
//more tables for other options types
UserOptions: UserID(PK), AbsolutePressPreference(FK), FluidFlowPreference(FK), etc

Since the values for each option type are unlikely to change you could also consider using enums rather than creating database tables, for example

public enum AbsolutePressOption
{
  psi,
  kpa
}

User options class (based on using enums)

public class UserOptions
{
  public UserOptions()
  {
    // Set defaults
    AbsolutePressPreference = AbsolutePressOption.psi;
  }
  public AbsolutePressOption AbsolutePressPreference { get; set; }
  public FluidFlowOption FluidFlowPreference { get; set; }
}

User

public class User
{
  public User()
  {
    // Assign default options when user initialised
    Options = new UserOptions();
  }
  .... // properties of user (ID, Name etc.)
  public UserOptions Options { get; set; }
}

If a user adopts the defaults, there is no need to save anything to the database. To change their options your controller might be something like

[HttpGet]
public ActionResult UserOptions(int ID)
{
  User user = UserRepository.Get(ID); // get user and user options from database

  ViewBag.AbsolutePressOptions = new SelectList(....
  return View(user);
}

Note, to create a SelectList from an enum for MVC-4 see here. Also it would be better to create a ViewModel that includes properties for the SelectLists.

The view

@model YourAssembly.User
// Some display properties of the user (Name etc.)
....
@using (Html.BeginForm())
{
  @Html.HiddenFor(m => m.ID) // so the user ID posts back
  @Html.LabelFor(m => m.Options.AbsolutePressPreference)
  @Html.DropDownFor(m => m.Options.AbsolutePressPreference, (SelectList)ViewBag.AbsolutePressOptions)
  // More selects for other options
  <input type="submit" value="Save Options" />
}

And the post method

[HttpGet]
public ActionResult UserOptions(User user)
{
  if (!ModelState.IsValid)
  {
    // Build select lists again
    return View(user)
  }
  UserRepository.SaveOptions(user) // Save options to database
  // Redirect to the user details page?
  return RedirectToAction("Details", new { ID = user.ID })
}

Finally, if a users preferences are used on most pages, I would consider creating a CustomPrincipal that writes the users preferences to the FormsAuthenticationTicket cookie so its avaliable in each request.

Community
  • 1
  • 1
  • the way I was heading was a dynamic approach. I know your design would work, but I would be editing the Controller Actions, Model, and View everytime a new preference was added. It also increases the coding initially. My approach allows me to iterate through the user's preferences with a `foreach (var preference in user.UOMDefaults)`. It seems clear on that part, but what I can't figure out is how to get all of the UOMMaster definitions separated for a individual dropdowns. – rwkiii Jul 20 '14 at 12:21
  • Also, I seriously considered using Enums. To break even, the UOMMasters table actually contains more data columns. All of the conversions are included too - the scale and shift factors, desired display precision, etc. I'm creating a class that wraps the UOMDefaults that performs all of the conversions. You added a couple of nice considerations for me, but I feel I decided against defining each of these preferences individually as you did in your answer. Afterall, there are maybe a couple dozen of them and their are more detail (columns) to their rows than I showed above. – rwkiii Jul 20 '14 at 12:28
  • OK, although I'd still create tables for each option type e.g. `AbsolutePressOptions: ID(PK), Name, IsDefault(bit), ScaleFactor, ShiftFactor, etc` so that you can retrieve all `AbsolutePressOptions` to create a `SelectList` for use in your dropdown, then save the ID value of the selected option in the `AbsolutePressPreference` column of the `UserOptions` table. –  Jul 20 '14 at 12:38
  • I guess my biggest hurdle is figuring out how to retrive the UOMMasters preferences and separate them, then add the user's selected preference for each one. I can already retrieve the entire UOMMasters into a list and I can easily get a list of the user's desired preferences for each, but all I have is 2 lists. I just don't see how it would be coded to separate the UOMMasters and set the user's preference. I think the POST Controller Action won't be so tough, but how to code the GET Controller Action to create the Model passed to the View is where I'm stuck. – rwkiii Jul 20 '14 at 21:15
  • I don't know enough about you application to help further, but a final comment. Conversion factors are static, so these could be static methods in your application rather than being stored in a database (database operations are expensive) –  Jul 20 '14 at 22:20
1

Should be something like this in your view

@foreach (string preference in Model.UOMPreferences.OrderBy(p=>p.SortOrder)
                                                .Select(p=>p.MeasureId )
                                                .Distinct())
{
    var preferences = Model.UOMPreferences.Where(p=>p.MeasureId = preference);
....
     @Html.DropDown(preference, new SelectList(preferences, "Id", "UOMId"))
....
}
Robbie Chiha
  • 409
  • 3
  • 9