MVC 3: Populating dropdown with users in ASP Membership Role "Manager" - asp.net-mvc-3

So, I am implementing ASP Membership and Role management in my application. I also have a second User table with all non-membership related information. I set the E-mail as the username in Membership and as the foreign key in my User table.
I am customizing the registration page to include a dropdown so a manager can be selected when the account is created. The list of managers is generated by finding all Membership users with the role "Manager" then creating a collection of Users where the foreign keys match the results.
List<string> managerNames = new List<string>(Roles.GetUsersInRole("Manager"));
var managers = from m in _db.Users where managerNames.Contains(m.Email) select m;
ViewBag.managers = managers;
Now I have to use that collection of users to populate a dropdown in my view that has the Name attribute set to "ManagerID" (to match my RegistrationModel), the value of each option set to the primary key of the User, and the displayed text in the dropdown showing the DisplayName of the User model.
I can go through the tedious task of looping through my "managers" collection and populating a separate SelectListItem, then passing the SelectListItem into a #Html.DropDown("ManagerID", newSelectListItem), but that seems excessive. Is there a more direct (or acceptable) way to do this?
EDIT
I added this to my controller
var selectList = new List<SelectListItem>();
foreach (var manager in managers)
{
selectList.Add(new SelectListItem(){
Value = manager.UserID.ToString(),
Text = manager.DisplayName,
Selected = false
});
}
ViewBag.managers = selectList;
and this to my view
#Html.DropDownList("ManagerID", (List<SelectListItem>)ViewBag.managers)
and it works. Is this still the best approach?

Is this still the best approach?
No. The best approach is to use view models and forget about the existence of ViewBag/ViewData. So start by designing a view model which will meet the requirements of your view (display a ddl of managers):
public class MyViewModel
{
[Required]
public int? SelectedManagerId { get; set; }
public IEnumerable<SelectListItem> Managers { get; set; }
}
and then have your controller action populate this view model and pass it to the view:
public ActionResult Foo()
{
var managers = ... query your repository to get them
var model = new MyViewModel
{
Managers = managers.Select(x => new SelectListItem
{
Value = x.UserID.ToString(),
Text = x.DisplayName
})
};
return View(model);
}
and finally in your strongly typed view:
#model MyViewModel
...
#Html.DropDownListFor(
x => x.SelectedManagerId,
Model.Managers,
"-- Select a manager --"
)
So everytime you employ ViewBag/ViewData in an ASP.NET MVC applications an alarm should ring telling you that there is a better way.

Related

Foreach ViewBag data gives 'object' does not contain a definition for 'var'

I have a controller that is building a query from Linq to Sql to pass into the ViewBag.products object. The problem is, I cannot loop using a foreach on it as I expected I could.
Here's the code in the controller building the query, with the .ToList() function applied.
var products = from bundles in db.Bundle
join bProducts in db.BundleProducts on bundles.bundleId equals bProducts.bundleID
join product in db.Products on bProducts.productID equals product.productID
join images in db.Images on product.productID equals images.productID
where bundles.bundleInactiveDate > DateTime.Now
select new {
product.productName,
product.productExcerpt,
images.imageID,
images.imageURL
};
ViewBag.products = products.ToList();
Since I am using a different model on the Index.cshtml for other items needed, I thought a simple Html.Partial could be used to include the viewbag loop. I have tried it with the same result with and without using the partial and simply by using the foreach in the index.cshtml. A snippet that includes the partial is below:
<div id="bundle_products">
<!--build out individual product icons/descriptions here--->
#Html.Partial("_homeBundle")
</div>
In my _homeBundle.cshtml file I have the following:
#foreach (var item in ViewBag.products)
{
#item
}
I am getting the ViewBag data, but I am getting the entire list as output as such:
{ productName = Awesomenes Game, productExcerpt = <b>Awesome game dude!</b>, imageID = 13, imageURL = HotWallpapers.me - 008.jpg }{ productName = RPG Strategy Game, productExcerpt = <i>Test product excerpt</i>, imageID = 14, imageURL = HotWallpapers.me - 014.jpg }
What I thought I could do was:
#foreach(var item in ViewBag.Products)
{
#item.productName
}
As you can see, in the output, productName = Awesomenes Game. However, I get the error 'object' does not contain a definition for 'productName' when I attempt this.
How can I output each "field" so to say individually in my loop so I can apply the proper HTML tags and styling necessary for my page?
Do I need to make a whole new ViewModel to do this, and then create a display template as referenced here: 'object' does not contain a definition for 'X'
Or can I do what I am attempting here?
*****UPDATE*****
In my Controller I now have the following:
var bundle = db.Bundle.Where(a => a.bundleInactiveDate > DateTime.Now);
var products = from bundles in db.Bundle
join bProducts in db.BundleProducts on bundles.bundleId equals bProducts.bundleID
join product in db.Products on bProducts.productID equals product.productID
join images in db.Images on product.productID equals images.productID
where bundles.bundleInactiveDate > DateTime.Now
select new {
product.productName,
product.productExcerpt,
images.imageID,
images.imageURL
};
var bundleContainer = new FullBundleModel();
bundleContainer.bundleItems = bundle;
return View(bundleContainer);
I have a model, FullBundleModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace JustBundleIt.Models
{
public class FullBundleModel
{
public IQueryable<Bundles> bundleItems { get; set; }
public IQueryable<Images> imageItems { get; set; }
}
}
and my View now has
#model IEnumerable<JustBundleIt.Models.FullBundleModel>
#foreach (var item in Model)
{
<div class="hp_bundle">
<h3>#Html.Display(item.bundleName)</h3>
</div>
}
If I remove IEnumerable from the model reference, the foreach errors out that there is no public definition for an enumerator.
In the #Html.Display(item.bundleName) it errors out that the model has no definition for bundleName. If I attempt
#foreach(var item in Model.bundleItems)
I get an error that bundleItems is not defined in the model.
So what don't I have wired up correctly to use the combined model?
Do I need to make a whole new ViewModel to do this, and then create a
display template as referenced here...
Darin's answer that you linked states the important concept: Anonymous types are not intended for use across assembly boundaries and unavailable to Razor. I would add that it's rarely a good idea to expose anonymous objects outside of their immediate context.
Creating a view model specifically for view consumption is almost always the correct approach. View models can be reused across views if you are presenting similar data.
It's not necessary to create a display template, but it can be useful if you want to reuse the display logic. HTML helpers can also fill a similar function of providing reusable display logic.
Having said all of that, it's not impossible to pass an anonymous type to a view, or to read an anonymous type's members. A RouteValueDictionary can take an anonymous type (even across assemblies) and read its properties. Reflection makes it possible to read the members regardless of visibility. While this has its uses, passing data to a View is not one of them.
More reading:
Can I pass an anonymous type to my ASP.NET MVC view?
Dynamic Anonymous type in Razor causes RuntimeBinderException
Why not create a new model that contains all of the data that you need?
Example One:
public class ContainerModel
{
public IQueryable<T> modelOne;
public IQueryable<T> modelTwo;
}
This will allow you to access either of your queries in Razor:
#model SomeNamespace.ContainerModel
#foreach (var item in Model.modelOne)
{
//Do stuff
}
I personally avoid using ViewBag at all and store everything I need in such models because it's NOT dynamic and forces everything to be strongly typed. I also believe that this gives you a clearly defined structure/intent.
And just for clarity's sake:
public ViewResult Index()
{
var queryOne = from p in db.tableOne
select p;
var queryTwo = from p in db.tableTwo
select p;
var containerModel = new ContainerModel();
containerModel.modelOne = queryOne;
containerModel.modelTwo = queryTwo;
return View(containerModel);
}
Example Two:
public class ContainerModel
{
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")] //Format: MM/dd/yyyy (Date)
public Nullable<DateTime> startDate { get; set; }
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")] //Format: MM/dd/yyyy (Date)
public Nullable<DateTime> endDate { get; set; }
public SelectList dropdown { get; set; }
public IQueryable<T> modelOne { get; set; }
public IQueryable<T> modelTwo { get; set; }
}
In this case you've stored 3 other items in the model with your 2 queries. You can use the Html helpers to create a Drop Down List in Razor:
#Html.DropDownList("dropdown", Model.dropdown)
And you can use the DisplayFor helper to display your dates as defined in your model with Data Annotations:
#Html.DisplayFor(a => a.startDate)
This is advantageous IMO because it allows you to define all of the data that you want to make use of in your View AND how you plan to format that data in a single place. Your Controller contains all of the business logic, your Model contains all of the data/formatting, and your View is only concerned with the content of your page.

Best way to bind the constant values into view (MVC3)

I have a constants values such as "Required","Optional", and "Hidden". I want this to bind in the dropdownlist. So far on what I've done is the below code, this is coded in the view. What is the best way to bind the constant values to the dropdownlist? I want to implement this in the controller and call it in the view.
#{
var dropdownList = new List<KeyValuePair<int, string>> { new KeyValuePair<int, string>(0, "Required"), new KeyValuePair<int, string>(1, "Optional"), new KeyValuePair<int, string>(2, "Hidden") };
var selectList = new SelectList(dropdownList, "key", "value", 0);
}
Bind the selectList in the Dropdownlist
#Html.DropDownListFor(model => model.EM_ReqTitle, selectList)
Judging by the property EM_RegTitle I'm guessing that the model you're using is auto-generated from a database in some way. Maybe Entity Framework? If this is the case, then you should be able to create a partial class in the same namespace as your ORM/Entity Framework entities and add extra properties. Something like:
public partial class MyModel
{
public SelectList MyConstantValues { get; set; }
}
You can then pass your SelectList with the rest of the model.
There are usually hangups from using ORM/EF entities through every layer in your MVC app and although it looks easy in code examples online, I would recommend creating your own View Model classes and using something like AutoMapper to fill these views. This way you're only passing the data that the views need and you avoid passing the DB row, which could contain other sensitive information that you do not want the user to view or change.
You can also move the logic to generate your static value Select Lists into your domain model, or into a service class to help keep reduce the amount of code and clutter in the controllers.
Hope this helps you in some way!
Example...
Your View Model (put this in your "Model" dir):
public class MyViewModel
{
public SelectList RegTitleSelectList { get; set; }
public int RegTitle { get; set; }
}
Your Controller (goes in the "Controllers" dir):
public class SimpleController : Controller
{
MyViewModel model = new MyViewModel();
model.RegTitle = myEfModelLoadedFromTheDb.EM_RegTitle;
model.RegTitleSelectList = // Code goes here to populate the select list.
return View(model);
}
Now right click the SimpleController class name in your editor and select "Add View...".
Create a new view, tick strongly typed and select your MyViewModel class as the model class.
Now edit the view and do something similar to what you were doing earlier in your code. You'll notice there should now be a #model line at the top of your view. This indicates that your view is a strongly typed view and uses the MyViewModel model.
If you get stuck, there are plenty of examples online to getting to basics with MVC and Strongly Typed Views.
You would prefer view model and populate it with data in controller.
class MyViewModel
{
public string ReqTitle { get; set; }
public SelectList SelectListItems { get; set; }
}
Then you can use:
#Html.DropDownListFor(model => model.EM_ReqTitle, model.SelectListItems)

Dynamically create random number of dropdownlists in MVC

I need to create a random number of dropdownlists in my view, based on the selected value of another dropdownlist. This is all done but my problem comes when I need to make the httppost because i never know how much data i need to save in my db.
In my model I have a list
public List<RoomToBooking> RoomsToBooking { get; set; }
that will get filled with x number of RoomToBooking when the Create view is rendered after the user makes a selction of dropdownlist 1:
var dogs = from d in db.Dogs
where d.Customer_ID == id
select d;
foreach (Dog item in dogs)
{
roomToBooking = new RoomToBooking();
roomToBooking.Customer_ID = id;
roomToBooking.Dog = item;
roomsToBookingList.Add(roomToBooking);
}
So I would like to create the same number of dropdownlist in my Create view
#Html.DropDownListFor(model => model.Booking.RoomToBooking, new SelectList(ViewBag.DeliveryTypes), new { #class = "selectbox" })
#Html.ValidationMessageFor(model => model.Booking.RoomToBooking)
So I in the end can be able to save it to my db
[HttpPost]
public ActionResult Create(EditBookingPensionViewModel model)
{
foreach (RoomToBooking item in objViewModel.RoomsToBooking)
{
//Save to db
}
}
I assume that I should use jquery to create the dropdownlists, but how do i create the dropdownlists so the selected values can be found in my viewmodel??
You may take a look at the following article. I slight adaption might be necessary for your scenario because you don't have add and remove buttons but instead you use the selected value of a dropdownlist to determine the number of dynamic rows to be added. But the concept is the exactly the same.

MVC ddl value after postback

I have a ddl which is populated with hours of day 01-23. This is on a form which is used to book an item of equipment. The hour is populated to a db field. The issue is this, when the booking form is opened to alter the time the ddl shows the hour that was booked, when changed though and the form is submitted the value passed on post is the initial value from db not the new selected hour.
this is the basic pieces of code. any idea why the newly selected ddl value is not passed to the model??
View
<%= Html.DropDownList("ddl_Hour", Model.ddlHour,
new { #class = "DropDown", style = "width: 40px” })%>
Model
private string _ddlHourSelectedValue = "0";
public SelectList ddlHour
{
get
{
return (new System.Web.Mvc.SelectList(_ddlHour, "intValue", "Text", Convert.ToInt32(_ddlHourSelectedValue)));
}
}
public string ddlHourSelectedValue
{
get
{
return _ddlHourSelectedValue;
}
set
{
_ddlHourSelectedValue = value;
}
}
param[6] = new SqlParameter("#Timeslot", ddlHourSelectedValue);
The field in your view is called "ddl_Hour" However is there a variable in your Model with the same name? Otherwise the MVC framework will not automatically populate the value in the model.
Two ways you could go about this.
1
In your controller methods that accepts a post, you can add the parameter: FormCollection fc to the method. This key value pair collection will allow you to fetch results from fields in the post data like so:
string selectedValue = fc["ddl_Hour"];
2
Or you can modify your model to include a variable with the same name as the drop down list so that it is automatically populated for you.
public string ddl_Hour { get; set; }
You should then be able to access the result of the drop down list selection on post from that variable.

ASP.Net MVC 3 ViewModel with Drop Down Lists

I am developing an ASP.Net MVC 3 web application. The app currently is connected to a database that has several tables, two of which are Category(catId, Name) and Site(siteID, Name).
I wish to create a view that has two drop down lists, one for each of the tables mentioned, so that the user can select from and then run a report. To do this I have created a viewModel to represent the two drop down lists
public class ReportSiteCategorySearchViewModel
{
public SelectList categoryList { get; set; }
public SelectList siteList { get; set; }
}
Then in my controller that returns the viewModel I have the following
public ActionResult getEquipmentByCategoryAndSite()
{
ReportSiteCategorySearchViewModel viewModel = new ReportSiteCategorySearchViewModel
{
categoryList = new SelectList(categoryService.GetAllCategories().ToList(), "categoryID", "categoryTitle"),
siteList = new SelectList(siteService.GetAllSites().ToList(), "siteID", "title")
};
return View(viewModel);
}
I then pass to a view which takes this viewModel and writes out the values to the drop downs
<div>
<label for="ddlSite">Sites</label>
#Html.DropDownList("ddlSite", Model.siteList, "All Sites")
<label for="ddlCatgeory">Categories</label>
#Html.DropDownList("ddlCatgeory", Model.categoryList, "All Categories")
</div>
This works, however, I am not sure this is the best way to do it. I am just wondering is my method correct, is there a better way to do this? Ie, what if I needed 5/6 more drop down lists from other tables, should I just add to the current viewModel etc?
Any feedback would be much appreciated.
Thank You.
You can create a viewModel of type List<SelectList> In your controller, add each table (as a SelectList as you're doing) to this model. Then pass the view the model, which is a list of SelectLists.
Then you can iterate through each value in your view:
<div>
#foreach(SelectList SL in Model)
{
<label for="ddl"+SL>SL.Title</label>
#Html.DropDownList("ddl"+SL.Title, sl.list, sl.items")
}
You may need to modify your list of SelectList to include the 'Title' or 'items' field. By doing it this way you can keep adding elements to the List, and you won't need to update the view.

Resources