binding dropdown in asp.net MVC registration - asp.net-mvc

I am desperately looking for help with customizing the registration page of my project. Based on available documentation, I've updated the identity model, the account view model and all works fine apart from the fact that my dropdownlist for gender or country code is manually hard coded. I can't figure out how to populate the dropdownlists from a database using the entity framework. I hence would highly appreciate if somebody could give me a quick walk through of how this is being achieved. I guess it should be fairly simple, but really been trying for days now.
In order to keep maintenance options simple, I want to go with a table on SQL that feeds into the dropdown. Let's take 'Gender' as example.
I have hence gone ahead and updated the AccountViewModels and added the following at the bottom:
public class GenderList
{
public int ID { get; set; }
public string Gender { get; set; }
}
After that, I went over to the IdentityModels and added the 'public DbSet line, so it looks as follows:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<GenderList> GenderLists { get; set; }
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
And that's where I think I get lost... it's obvious that I have to update the AccountController as well (which I have done...) but I'm not quite sure how to best approach it, as the existing Register.cshtml would not allow me to load multiple models, etc.
For what concerns the controller, I've tried (and failed with...) the following:
public ActionResult GetGender()
{
GenderList db = new GenderList();
ViewBag.Gender = new SelectList(db.Gender, "ID", "Gender");
return View();
}
And last but not least, the relevant section of my Register.cshtml:
#Html.LabelFor(m => m.Gender, new { #id = "registerGenderLabel", #class = "registerLabel col-xs-offset-1 col-xs-10 col-sm-offset-1 col-sm-10 col-md-offset-1 col-md-3 col-lg-offset-1 col-lg-4" })
#*#{ var gender = new List<SelectListItem>
{
new SelectListItem{ Text = "female", Value = "female" },
new SelectListItem { Text = "male", Value = "male"}
};
}*#
#Html.DropDownListFor("Gender","Select")

I'm not sure I entirely understand what you are asking. But for select lists, just add a SelectListItem as a Property of the Model that is the main model for the View.
In model.
public <List>SelectListItems AddressPurposes {get; set;}
public string AddressPurpose {get; set;}
Populate this in the controller as use the DropDownListFor as such, replacing the strings with the values from your db in a loop. Here I'm just adding a hard coded value but you can obviously substitute whatever:
model.AddressPurposes = new List<SelectListItem>() { };
model.AddressPurposes.Add(new SelectListItem() { Text = "New Address", Value = "New Address" });
In view:
#Html.DropDownListFor(m => m.AddressPurpose, Model.AddressPurposes, new { #class = "input-sm" })

Related

ASP.Net Filter Dropdown list items from multiple tables

Summary:
I'm trying to use two DropDownList controls to filter the data that is currently being sorted and displayed in a view.
What we are going to learn
Creating the ViewController for One to Many and Many-to-Many relationships that could Filter the data using DropDownList
Possible Causes
If my DropdownList code is not terrible wrong, The ViewModel I'm using to display the data has no proper support for the DropDownList items.
In other words, the RazorView and my ViewModels are not compatible for what I'm trying to achieve. If I try to change my ViewModel or RazorView, I get an eldless loop of errors for my existing code.
OR The Linq Query needs an expert attention
Here is FilterViewModel.cs
public IEnumerable <App> Apps { get; set; }
public IEnumerable <Language> Languages { get; set; }
public IEnumerable <Platform> Platforms { get; set; }
public IEnumerable <AgeGroup> AgeGroups { get; set; }
public IEnumerable <ProductCode> ProductCodes { get; set; }
Here is AppsController.cs
public ActionResult FilterApps(App app)
{
var apps = _context.Apps.ToList();
var languages = _context.Languages.ToList();
var productCodes = _context.ProductCodes.ToList();
var platforms = _context.Platforms.ToList();
var ageGroups = _context.AgeGroups.ToList();
var viewModel = new FilterViewModel
{
AgeGroups = ageGroups,
Languages = languages,
Platforms = platforms,
ProductCodes = productCodes,
Apps = apps
.Where(a => a.LanguageId == app.LanguageId && a.PlatformId == app.PlatformId)
// I also tried all possible combinations :(a.Lanage.Id etc)
};
return View("FilterApps", viewModel);
}
Here is the FilterApps.cshtml
#model Marketing.ViewModels.FilterViewModel
<h2>FilterApps</h2>
#using (Html.BeginForm("FilterApps", "Apps", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.DropDownListFor( m => m.Languages,
new SelectList(Model.Languages, "Id", "Name"),"Select Language",
new { #class = "form-control", #id = "dropDown" })
#Html.DropDownListFor(m => m.Platforms,
new SelectList(Model.Platforms, "Id", "Name"), "Select Platform",
new { #onchange = "this.form.submit();",
#class = "form-control", #id = "dropDown" })
</div>
}
//The existing code below is working fine so far.
#foreach (var group in Model.AgeGroups)
{
<h4>#group.Name</h4>
#foreach (var app in Model.Apps.OrderBy(a => a.AppOrder))
{
if (app.AgeGroupId == group.Id)
{
#app.ProductCode.Name
#app.Name
#app.Platform.Name
}
}
}
Probably unnecessary but I hope the additional information will help.
Additional Information
The App.cs is referencing all other tables e.g.
public Language Language { get; set; }
public int LanguageId { get; set; }
public Platform Platform { get; set; }
public int PlatformId { get; set; }
and so on...
What I have already tried
Several breakpoints and Logs to track the data, I also tried to use the following but it ruins my existing sorting and grouping.
public App App { get; set; } //Instead of the IEnumerable<App>
There are multiple issues with your code.
First you cannot bind a <select> element to a collection of complex objects. A <select> posts back the value of its selected option (which will be an int assuming the Id property of Language is int).
Next the view in the model is FilterViewModel (and your generating form controls with name attributes based on those properties), but your posting back to a different model (App) which does not contain those properties so nothing would bind anyway.
Your adding a null label option ("Select Language") and if that were selected, it would post a null value which would cause your query to fail.
There are also some bad practices which I have noted below.
Your view model should be
public class AppsFilterVM
{
public int? Language { get; set; }
public int? Platform { get; set; }
public IEnumerable<SelectListItem> LanguageOptions { get; set; }
public IEnumerable<SelectListItem> PlatformOptions { get; set; }
...
public IEnumerable <App> Apps { get; set; }
}
Its not clear what AgeGroups and ProductCodes are for so I have omitted them in the code above, and from your comments, I have assumed that the user can filter by either Language or Platform or both
The controller code would be
public ActionResult FilterApps(AppsFilterVM model)
{
var apps = _context.Apps;
if (model.Language.HasValue)
{
apps = apps.Where(x => x.LanguageId == model.Language.Value);
}
if (model.Platform.HasValue)
{
apps = apps.Where(x => x.PlatformId == model.Platform.Value);
}
model.Apps = apps;
ConfigureViewModel(model);
return View(model);
}
private void ConfigureViewModel(AppsFilterVM model)
{
// populate the selectlists
var languages = _context.Languages;
var platforms = _context.Platforms
model.LanguageOptions = new SelectList(languages, "Id", "Name");
model.PlatformOptions = new SelectList(platforms , "Id", "Name");
}
Then in the view (note its making a GET, not a POST)
#model.AppsFilterVM
....
#using (Html.BeginForm("FilterApps", "Apps", FormMethod.Get))
{
#Html.LabelFor(m => m.Language)
#Html.DropdownListFor(m => m.Language, Model.LanguageOptions, "No filter")
#Html.ValidationMessageFor(m => m.Language)
#Html.LabelFor(m => m.Platform)
#Html.DropdownListFor(m => m.Platform, Model.PlatformOptions, "No filter")
#Html.ValidationMessageFor(m => m.Platform)
<input type="submit" value="Filter" />
}
#foreach (var group in Model.AgeGroups)
{
....
There a a few other thing you should not be doing. Your giving both <select> elements the same id attribute which is invalid html (the DropDownListFor() method already generates a unique id based on the property name).
You should not submit a form based on the change event of a <select> Not only is it unexpected behavior, if a user uses the keyboard to navigate through options (e.g. using the arrow keys, or typing a character to go to the first option starting with that letter, then the form will be immediately submitted. In addition, the user might select an option from the 2nd dropdownlist first, which would immediately post before they have a chance to select the option in the first one. Allow the user to make their selections, check them, and then submit the form when they choose to.
Your view should not contain linq queries, and your grouping and ordering should be done in the controller before you pass the model to the view. Your Apps property should in fact be a view model containing a property for the group name, and a collection property for the Apps, (similar to the view models in your previous question) so that the view is simply
#foreach(var group in Model.AgeGroups)
{
#group.Name
foreach (var app in group.Apps)
{
#app.ProductCode
#app.Name
#app.Platform
}
}
You should also consider using ajax to submit your form, which would call separate server method that returns a partial view of just the Apps, and update the DOM in the success callback, which would improve performance. For an example, refer Rendering partial view on button click in ASP.NET MVC.

Unable to add and display list of ASP.Net Identity Users to object model (MVC5)

I have a Ticket model with some basic ticket information.
What I want to do now is I want to be able to assign Agent to each Ticket.
For the Agent, I want to use the ASP.Net Identity Users which I already have.
I want to add property called AgentName and AgentID to this Ticket model.
In my Index View, I want to see a single Agent Name assigned to AgentName property and When I am editing a Ticket I want to have a drop down list of all available ASP.Net Identity Users (Agent Name) so that I can assign the ticket to.
I want to also have the ability to query all assigned Ticket to an specific Agent using AgentID so somehow I need to link each Ticket to an Agent using the AgentID.
How can I make this happen with code? Any help is appreciated.
So far I have this:
Public class Ticket
{
public int Id { get; set; }
Display(Name = "Ticket comes from")]
public string Source { get; set; }
}
In my ticket Edit(int id) HttpGet method I am fetching a Ticket information with the following code.
//HttpGet
public ActionResult Edit(int id)
{
Models.Ticket model = db.Ticket
.Include("Source")
.SingleOrDefault(m => m.Id == id);
if (model == null)
{
return new HttpNotFoundResult();
}
return View(model);
}
What I have tried
I have tried changing my Ticket model to something like this
Public class Ticket
{
public int Id { get; set; }
Display(Name = "Ticket comes from")]
public string Source { get; set; }
//....
// Now I want to have a property called AgentName and AgentID
//...something like this
public string AgentName {get; set;} //***I want this to display the User Name of the User in Index View page. But, I also want a dropdown list of user when I am in Edit mode so that I can change the AssignedToUser to have different user.
public string AgentID {get; set;}
}
To get all ASP.Net Identity User (Agents) I have something like this:
ViewBag.Agents = new SelectList(db.Users);
To display as drop down list in Edit mode I have something like this:
<div class="form-group">
#Html.LabelFor(model => model.AgentName, new { #class = "col-sm-4 control-label" })
<div class="col-sm-8">
#Html.DropDownList("Agents", ViewBag.Agents as SelectList, String.Empty, new { #class = "form-control" })
</div>
</div>
But nothing is working. When I am editing a Ticket, the Agent drop down list display some weir character like shown in the picture here. I want this to be a list of User Name.
DEBUG
#nurdyguy Put a debug stop on the line in the controller where you do ViewBag.Agents = new SelectList(db.Users);
This is what I got:
This is not an Entity Framework issue, this is just the default behavior for DropDown when you give it a complex object. Try keeping your View code the same and try something like the following in your Controller:
// change Name to the display property you want to show up in the dropdown
// source, value, display
ViewBag.Agents = new SelectList( db.Users, "Id", "Name");
References:
DropDownList Property to display
ASP.NET MVC Html.DropDownList SelectedValue

Correct way to display a list in a dropdown using mvc

I know there are a lot of similar question here but none seem quite the same as mine.
In my View:
#model LocalInformedMotionServer.Models.FeedData
#Html.DropDownList("Premise", Model.LoadUp("key"), new { #style = "width: 218px;height:35px;" })
In my controller:
public class CloudController : Controller
{
public IEnumerable<wsCommon.Premise> Premises { get; set; }
public ActionResult Feed(string key)
{
var feedData = new FeedData();
Premises= feedData.LoadUp(key);
return View(feedData);
}
}
In my Model:
public class FeedData
{
public IEnumerable<wsCommon.Premise> LoadUp(string saltKey)
{
Premises premises = new InformedBiz.Premises();
return premises.GetPremises(saltKey);
}
}
It errors because the variable:
"key"
in this call:
Model.LoadUp("key")
is being read in as'null' in my controller method.
Of course as this is all new to me I could be doing this all wrong..
ADDITIONAL:
In my CloudController Class I have this:
public class CloudController : Controller
{
public ActionResult Feed(string saltKey)
{
var feedData = new FeedData();
feedData.LoadUp(saltKey);
return View(feedData);
}
public ActionResult Index()
{
return View();
}
public ActionResult LogIn()
{
return View();
}
}
I'm not sure what your Premise class looks like, but I usually use an IEnumberable of SelectListItem for drop downs in my views. So you could do something like this:
public IEnumerable<SelectListItem> LoadUp(string saltKey)
{
Premises premises = new InformedBiz.Premises();
return premises.GetPremises(saltKey).Select(
p => new SelectListItem { Text = p.Name, Value = z.PremiseId.ToString() }
);
}
You'll also need to create a Post ActionResult method that accepts the model in your view (FeedData) as well as wrap your DropDownList control in a Html.BeginForm, to post results to the controller. Hope this makes a bit of sense.
You have not posted the properties of your FeedData model but assuming it contains a property which is typeof Premise and you want to be able to select a Premise from a collection, then using a view model that represents what you want to display/edit is the recommended approach (refer View Model Design And Use In Razor Views and What is ViewModel in MVC?)
You view model might look like
public class FeedDataVM
{
.....
[Display(Name = "Premise")]
[Required(ErrorMessage = "Please select a Premise")]
public int? SelectedPremise { get; set; }
....
public SelectList PremiseList { get; set; }
}
and in your controller (not sure what saltKey is for?)
public ActionResult Feed(string saltKey)
{
FeedDataVM model = new FeedDataVM();
IEnumerable<Premise> premises = // get the collection of premise objects from your repository
// assuming you want to display the name property of premise, but post back the key property
model.PremiseList = new SelectList(premises, "key", "name");
return View(model);
}
View
#model FeedDataVM
#using(Html.BeginForm())
{
....
#Html.LabelFor(m => m.SelectedPremise)
#Html.DropDownListFor(m => m.SelectedPremise, Model.PremiseList, "-Please select")
#Html.ValidationMessageFor(m => m.SelectedPremise)
....
<input type="submit" value="Save" />
}
and the POST method
public ActionResult Feed(FeedDataVM model)
{
// model.SelectedPremise contains the value of the selected option as defined by the key property of Premise
}
Side note: Your FeedData model contains a method to retrieve a collection of Premise which appears to be calling another service. You should avoid this type of design which makes it difficult to debug and unit test. Your controller is responsible for initializing/getting you data models and view models and for populating/mapping their properties.

Formatting a Drop Down List in ASP.NET MVC

I'm building an app with ASP.NET MVC 4. I'm binding my model to a view. In my view, I need a drop down list. That drop down list needs to show quarters. The quarters should be displayed as "Q1", "Q2", "Q3", and "Q4". My model, only has quarter numbers. They are defined like this:
public List<short> Quarters = new List<short>() { get; set; }
public short? SelectedQuarter = null;
public void Initialize() {
Quarters.Add(1);
Quarters.Add(2);
Quarters.Add(3);
Quarters.Add(4);
}
Somehow, I need to prepend "Q" to each value. However, I'm not sure how to do this in ASP.NET MVC. How does someone do this?
Thanks!
Create a SelectList to be used by DropdownListFor() so that you bind the selected option to property SelectedQuarter, but display the 'friendly' name.
View model
public class MyViewModel
{
[Display(Name = "Quarter")]
[Required]
public short? SelectedQuarter { get; set; } // must be a property, not a field!
IEnumerable<SelectListItem> QuarterList { get; set; }
}
Controller
public ActionResult Edit()
{
MyViewModel model = new MyViewModel();
ConfigureViewModel(model);
return View(model);
}
public ActionResult Edit(MyViewModel model)
{
if(!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// model.SelectedQuarter contains the selected value
}
private void ConfigureViewModel(model)
{
model.SelectedQuarter = new List<SelectListItem>()
{
new SelectListItem() { Value = "1", Text = "Q1" },
new SelectListItem() { Value = "2", Text = "Q2" },
new SelectListItem() { Value = "3", Text = "Q3" },
new SelectListItem() { Value = "4", Text = "Q4" },
}
}
View
#model MyViewModel
#using(Html.BeginForm())
{
#Html.LabelFor(m => m.SelectedQuarter)
#Html.DropDownListFor(m => m.SelectedQuarter, Model.QuarterList, "-Please select-")
#Html.ValidationMessageFor(m => m.SelectedQuarter)
<input type="submit" />
}
Assuming you have this property:
public List<short> Quarters { get; set; }
Then in your view or any other consuming code you can generate a list of strings with something like:
model.Quarters.Select(q => "Q" + q)
or:
model.Quarters.Select(q => string.Format("Q{0}", q))
However, semantically it really feels like this belongs on a view model and not in consuming code. Ideally the view should only ever need to bind directly to properties on the view model, not transform those properties. Something like this:
public IEnumerable<string> QuartersDisplay
{
get { return Quarters.Select(q => string.Format("Q{0}", q)); }
}
Then consuming code can just bind to that property:
model.QuartersDisplay
(If the model is a domain model then I'd recommend introducing a view model between the domain and the view, since this wouldn't belong on a domain model.)
Thinking about this a little more... Do you want one property with both the displays and the backing values for the drop down list? That would likely be a IDictionary<short, string>, I imagine? Something like this:
public IDictionary<short, string> QuartersOptions
{
get { return Quarters.ToDictionary(q => q, q => string.Format("Q{0}", q)); }
}
In which case you'd bind to that property:
model.QuartersOptions
Keep in mind that a drop down list often binds to two things. The property which holds the list of possible values (which is what we've built here) and the property which holds the selected value (which remains your SelectedQuarter property).

Per-row inline editor with individual Submit buttons

I have a scenario where I would like to have a list of editable items with per-item submit button, i.e. it should be allowed to submit only one item at a time.
I tried different approaches and the best answer I could find was this one: How to use multiple form elements in ASP.NET MVC, but the problem is that it assumes a single form and therefore a single Submit button.
So, here is what I tried so far. The Model:
namespace TestWebApplication4.Models
{
public class Entity
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
public class TestModel
{
public IEnumerable<Entity> Entities { get; set; }
}
}
so I have a collection of Entity objects, which I want to display collectively, and edit and submit individually. The way I'm trying to do it is to have multiple forms on one View:
#using TestWebApplication4.Models
#model TestWebApplication4.Models.TestModel
#{
int i = 0;
foreach (Entity entity in Model.Entities)
{
i++;
<div>
#using (Html.BeginForm("Index"))
{
#Html.HiddenFor(model => entity.Id)
#Html.TextAreaFor(model => entity.Name, new {id = "entity_Name" + i})
#Html.ValidationMessageFor(model => entity.Name)
<button type="submit">Submit</button>
}
</div>
}
}
and the Controller:
namespace TestWebApplication4.Controllers
{
public class TestController : Controller
{
public ActionResult Index()
{
var model = BuildModel();
return View(model);
}
[HttpPost]
public ActionResult Index(Entity entity)
{
if (!ModelState.IsValid)
{
var model = BuildModel(entity);
return View(model);
}
return RedirectToAction("Index");
}
private static TestModel BuildModel(Entity entity = null)
{
var entities = new List<Entity>
{
new Entity {Id = 11, Name = "Product A"},
new Entity {Id = 12, Name = "Product B"},
new Entity {Id = 13, Name = "Product C"},
};
if (entity != null)
entities[entities.IndexOf(entities.Single(e => e.Id == entity.Id))] = entity;
return new TestModel {Entities = entities};
}
}
}
As you can see, I'm trying to receive individual Entity object in [HttpPost] Index action, and if it is invalid (Name value is not provided) to re-build the entire list and replace specific Entity with the invalid one (all of this happens in BuildModel method), to display the validation message. This however does not work properly, as the resulting view contains 3 entries, all with the same invalid Entity, i.e. I get three empty TextAreas and three "The Name field is required." validation messages.
Can anyone help me figure this out? I assume I'm doing something against the conventions and that it is probably completely wrong approach to this problem, however I could not find a solution anywhere myself, so any directions would be appreciated. Thanks in advance.

Resources