Controller to controller communication in C# MVC - c#

I want to use the list of state controller in a district controller. Are there any better ideas.
I have tried one which is working
I put this code in the district controller by using constructor injection.
In this case, the entire code needs to be placed in the district controller.
Is there any way to reduce the code. A better way?
#region StateDropDown
public List<SelectListItem> StateDropDown()
{
List<SelectListItem> selectListItem = new List<SelectListItem>();
List<StateViewModel> stateList = Mapper.Map<List<State>, List<StateViewModel>>(_stateBusiness.GetStateForSelectList());
if (stateList != null)
foreach (StateViewModel state in stateList)
{
selectListItem.Add(new SelectListItem
{
Text = state.Description,
Value = state.Code.ToString(),
Selected = false
});
}
return selectListItem;
}
#endregion StateDropDown

This is what the term 'reusability' is invented for. Place the code in another file and make calls to it from any number of controllers you want, like code below.
//StateBusiness.cs
public class StateBusiness
{
public List<SelectListItem> GetStatesForDropdown()
{
//your logic here
return new List<SelectListItem>();
}
}
//StateController.cs
public class StateController : Controller
{
var state = new StateBusiness();
public ActionResult Index()
{
//call your code here
var states = state.GetStatesForDropdown();
//and do whatever you want
ViewBag.states = states;
return View();
}
}
//DistrictController.cs
public class DistrictController : Controller
{
var state = new StateBusiness();
public ActionResult Index()
{
//call it from here just the same
var states = state.GetStatesForDropdown();
ViewBag.states = states;
return View();
}
}

I don't know about better, but you could shorten this considerably using linq Select.
Mapper.Map<List<State>, List<StateViewModel>>(_stateBusiness.GetStateForSelectList())?
.Select(state => new SelectListItem
{
Text = state.Description,
Value = state.Code.ToString(),
Selected = false
}))?.ToList() ?? List<SelectListItem>();

If you using core one option might be to keep this off the controller and use a TagHelper this will let you inject the options into the tag with a simple attribute state-items reducing controller dependencies and keeping this state off the ViewBag while being more reusable.
Here is how it wold look in the view:
<select asp-for="State" state-items />
The TagHelper:
[HtmlTargetElement("select", Attributes = "state-items")]
public class StateItemsTagHelper : TagHelper {
private readonly StateBusiness _stateBusiness;
[HtmlAttributeName("asp-for")]
public ModelExpression For { get; set; }
public StateItemsTagHelper(StateBusiness stateBusiness) {
this._stateBusiness = stateBusiness;
}
public override void Process(TagHelperContext context, TagHelperOutput output) {
content.TagMode = TagMode.StartTagAndEndTag;
var value = For?.Model as string;
var items = _stateBusiness.GetStateForSelectList()?.Select(state => new SelectListItem {
Text = state.Description,
Value = state.Code.ToString(),
Selected = value == state.Code.ToString()
})) ?? Enumerable.Empty<SelectListItem>();
foreach(var item in items) {
output.Content.AppendHtml(item.ToHtmlContent());
}
}
}
For reusability item.ToHtmlContent is an extension method:
public static IHtmlContent ToHtmlContent(this SelectListItem item) {
var option = new TagBuilder("option");
option.Attributes.Add("value", item.Value);
if(item.Selected) {
option.Attributes.Add("selected", "selected");
}
option.InnerHtml.Append(item.Text);
return option;
}

Related

Set mvc dropdownlist value bound to object array

I have a razor view in ASP.NET MVC looping over an array of objects from my model and generating corresponding html controls.
My html elements are properly bound, except my drop down lists who can't seem to select the value provided to them by the model.
My view: (in the hereby case, I'm simply displaying a list of countries)
#for (var i = 0; i < Model.answers.Count(); i++)
{
<div class="form-group">
...
#switch (Model.answers[i].Statement.QuestionType)
{
...
case ExternalEnums.QuestionTypeEnum.country:
#Html.DropDownListFor(Model => Model.answers[i].Value,
new SelectList(Model.Pays, "Value", "Text"))
break;
}
...
</div>
}
My view controller, generating the country list items and retrieving the existing model entries:
public class HomeIndexViewModel
{
private QuestionsModelContainer dbContext;
private AdmcommonEntities admCommonContext;
...
public List<Answer> answers { get; private set; }
private IEnumerable<SelectListItem> _countries;
public IEnumerable<SelectListItem> Pays
{
get
{
if (_countries == null)
SetCountries();
return _countries;
}
}
public HomeIndexViewModel()
{
Init(-1, null);
}
public HomeIndexViewModel(int page, string _pageWideError = null)
{
Init(page, _pageWideError);
}
private void Init(int page, string _pageWideError = null)
{
dbContext = new QuestionsModelContainer();
PageNum = page;
pageWideError = _pageWideError;
answers = GetAnswers();
...
}
private void SetCountries()
{
using (admCommonContext = new AdmcommonEntities())
{
var localEntities = admCommonContext.Pays.ToList();
var localList = new List<SelectListItem>();
localList.Add(new SelectListItem());
foreach (var item in localEntities)
{
var newItemList = new SelectListItem();
newItemList.Text = item.Libelle;
newItemList.Value = item.Libelle;
localList.Add(newItemList);
}
_countries = localList;
}
}
public List<Statement> GetStatements()
{
var statements = dbContext.StatementSet.Where(w => w.Page == PageNum).OrderBy(w => w.Order).ToList();
return statements;
}
public List<Answer> GetAnswers()
{
var statements = GetStatements();
var ExistingAnswers = new List<Answer>();
if (AdminPermissionManager.IsUserAuthenticated()) //Loading existing entries.
ExistingAnswers = Answer.GetExistingAnswers(statements, dbContext);
var answers = new List<Answer>();
foreach (var item in statements)
{
var answer = ExistingAnswers.Where(w => w.StatementId == item.Id).FirstOrDefault();
if (answer == null)
{
answer = new Answer();
answer.StatementId = item.Id;
answer.Statement = item;
}
answers.Add(answer);
}
return answers;
}
}
My model class, simply containing the value I'm trying to display:
[MetadataType(typeof(AnswerMetaData))]
public partial class Answer
{
...
public static List<Answer> GetExistingAnswers(List<int> statementIds, QuestionsModelContainer dbContext)
{
List<Answer> ExistingAnswers;
var usercode = AdminPermissionManager.GetUserCode();
ExistingAnswers = dbContext.AnswerSet.Where(w => statementIds.Contains(w.StatementId) && w.ChildCode == usercode).ToList();
return ExistingAnswers;
}
public static List<Answer> GetExistingAnswers(List<Statement> statements, QuestionsModelContainer dbContext)
{
var statementIds = statements.Select(w => w.Id).ToList();
return GetExistingAnswers(statementIds, dbContext);
}
}
public class AnswerMetaData
{
[InternalValidation]
public string Value { get; set; }
private class InternalValidationAttribute : ValidationAttribute
{
...
}
}
I'm sure there's something very obvious that I'm missing, but can't figure out what exactly :/...
You're nearly there actually, this part in the View:
#Html.DropDownListFor(
Model => Model.answers[i].Value,
new SelectList(Model.Pays, "Value", "Text")
)
You create a new selectlist - each time, but you already have a IEnumerable<SelectListItem> created, so you don't have to recreate that list. The only thing you might be missing (most likely) is the "Selected" item option.
If you already have a value selected (and it isn't the first one) it will not be selected dropdown option - also because you pass the value of the selected option as the "ID" of the field (not the actual value) - DropDownListFor is kinda weird in that regard.
So you want to change your #Html.DropDownListFor to something like this:
#Html.DropDownListFor(
Model => Model.answers[i].Name,
Pays(Model.answers[i].Value)
)
When that being done you should change your property "Pays" in the ViewModel to a method that accepts a value (idk what you're using, but let's assume it's string) - to something along the lines of this:
public IEnumerable<SelectListItem> Pays(string selectedValue)
{
if (_countries == null) SetCountries();
var value = new List<SelectListItem>();
foreach(var item in _countries)
{
item.Selected = (item.Value == selectedValue);
value.Add(item);
}
return value;
}
This above is a bit pseudocoded since I'm typing this from memory, but it should get you into the correct direction. Also remember to check with the inspect element in the browser if the dropdown HTML element really has the correct name attribute.

MVC4: populate a drop down list from a custom class

I have a class that I populate when a user navigates to a certain page, /Home/About, within my MVC4 application. I populate a class with data and I would like to have in my view that data in a drop down list.
My class looks like this: (UPDATED)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
public class WorkSection : List<WorkSection>
{
[Required]
[Display(Name = "WorkSection")]
public int ID { get; set; }
public string Code { get; set; }
public SelectList WorkSections { get; set; }
public WorkSection()
{
// Default Constructor
}
public WorkSection(int id, string code)
{
this.ID = ws_id;
this.Code = code;
}
}
How do I take this populated List of type WorkSection and make that the data source for my drop down list? I would like to display the Code and Source field in a concatenated fashion, like "Code:Source" within the drop down list with the ID as the selected item's value.
UPDATE for ActionResult, where code will be called from on /Home/About
public ActionResult About()
{
WorkSection model = new WorkSection();
OracleConnection con = new OracleConnection();
con.ConnectionString = "omitted";
try
{
con.Open();
}
catch (Exception ex)
{
throw ex;
}
try
{
OracleDataReader reader = null;
// Work Section
OracleCommand cmd = new OracleCommand("SELECT ID, CODE FROM MyTable ORDER BY CODE", con);
reader = cmd.ExecuteReader();
while (reader.Read())
{
model.Add(new WorkSection()
{
ID = Int16.Parse(reader["ID"].ToString()),
Code = reader["CODE"].ToString()
});
}
model.WorkSections = BuildSelectList(model.WorkSections, m => m.ID, m => m.Code);
con.Close();
con.Dispose();
}
catch (Exception ex)
{
throw ex;
}
return View(model);
}
First up, we need a view model to encapsulate the data for the view:
public class TestViewModel
{
[Required]
[Display(Name = "Work section")]
// This represents the selected ID on the dropdown
public int WorkSectionId { get; set; }
// The dropdown itself
public SelectList WorkSections { get; set; }
// other properties
}
Next up, we need a way to populate the SelectList. I wrote a custom method a while ago to do just that:
private SelectList BuildSelectList<TSource>(IEnumerable<TSource> source,
Expression<Func<TSource, int>> valueKey, Expression<Func<TSource, string>> textKey,
object selectedValue = null)
{
var selectedValueKey = ((MemberExpression)(MemberExpression)valueKey.Body).Member.Name;
var selectedTextKey = ((MemberExpression)(MemberExpression)textKey.Body).Member.Name;
return new SelectList(source, selectedValueKey, selectedTextKey, selectedValue);
}
This uses expression trees for type-safety, ensuring problems are caught at compile-time, rather than run-time. SelectList also uses one property for the text key and one for the value key. In your situation, this obviously creates a problem, because you want to combine Code and Source to form the text key. In order to get around that, you'll need to create a new property in WorkSection that combines both:
public string CodeSource
{
get { return this.Code + ":" + this.Source; }
}
That way, you can use that to create the SelectList as normal. To do that, your action might like something like:
public ActionResult Index()
{
var workSections = // ... fetch from database
TestViewModel model = new TestViewModel();
model.WorkSections = BuildSelectList(workSections, m => m.ID, m => m.CodeSource);
return View(model);
}
You can use that in the view like so:
#Html.DropDownListFor(m => m.WorkSectionId, Model.WorkSections, "--Please Select--")
#Html.ValidationMessageFor(m => m.WorkSectionId)
One final note on BuildSelectList. The method has saved me a lot of time when dealing with dropdowns in general. So much so that I now define it as a public method on a base controller, which I then derive all of my controllers from. However, if you want to do that, you'll want to mark it with the [NonAction] attribute so it doesn't interfere with routing.
Update per comments
public class BaseController : Controller
{
[NonAction]
public SelectList BuildSelectList<TSource>(IEnumerable<TSource> source,
Expression<Func<TSource, int>> valueKey, Expression<Func<TSource, string>> textKey,
object selectedValue = null)
{
var selectedValueKey = ((MemberExpression)(MemberExpression)valueKey.Body).Member.Name;
var selectedTextKey = ((MemberExpression)(MemberExpression)textKey.Body).Member.Name;
return new SelectList(source, selectedValueKey, selectedTextKey, selectedValue);
}
}
Then you'd derive your controllers from BaseController instead:
public HomeController : BaseController
{
//
}
#Hmtl.DropdownListFor(m=>m.YourNameForSelectedWorkSectionId, Model.WorkSections.Select(x => new SelectListItem { Text = x.Code +":"+x.Source, Value = x.ID}))

MVC using methods from a helpers namespace

I have the following in ~/Helpers/Helpers.cs:
namespace AdjusterSave.Helpers
{
public class Helpers : Controller
{
// various methods such as the following...
public void GetDropdowns()
{
}
}
}
I am trying to use these in my ~/Controllers/AdjusterController.cs file by including it like so:
using AdjusterSave.Helpers;
However, I continue to get the following error when trying to use the methods. When I call this:
GetDropdowns();
I get this error:
The name 'GetDropdowns' does not exist in the current context.
Edit:
Trying to use method like so (in ~/Controllers/AdjusterController.cs):
public ActionResult ViewProfile()
{
// a bunch of code like this:
User user = db.Users.Where(x => x.username == HttpContext.User.Identity.Name).FirstOrDefault();
AdjusterViewProfileInfo model = new AdjusterViewProfileInfo();
// get name
model.namePrefix = user.namePrefix;
model.firstName = user.firstName;
model.middleInitial = user.middleInitial;
model.lastName = user.lastName;
model.nameSuffix = user.nameSuffix;
// end then, finally,
GetDropdowns();
// followed by...
TempData["CurrentPage"] = "ViewProfile";
return View("", _layout, model);
}
Edit:
GetDropdowns Example:
public void GetDropdowns(this Controller controller)
{
// get name prefixes
List<SelectListItem> prefixList = new List<SelectListItem>();
prefixList.Add(new SelectListItem { Value = "Mr.", Text = "Mr." });
prefixList.Add(new SelectListItem { Value = "Mrs.", Text = "Mrs." });
prefixList.Add(new SelectListItem { Value = "Ms.", Text = "Ms." });
ViewBag.PrefixList = new SelectList(prefixList, "Value", "Text");
}
You are doing it wrong. What you need to do is to create a static class like this:
public static class Helpers
{
public static void GetDropdowns(this Controller controller)
{
// var username = controller.HttpContext.User.Identity.Name;
// get name prefixes
List<SelectListItem> prefixList = new List<SelectListItem>();
prefixList.Add(new SelectListItem { Value = "Mr.", Text = "Mr." });
prefixList.Add(new SelectListItem { Value = "Mrs.", Text = "Mrs." });
prefixList.Add(new SelectListItem { Value = "Ms.", Text = "Ms." });
controller.ViewBag.PrefixList = new SelectList(prefixList, "Value", "Text");
}
}
And, you can use it in your Controller like this:
this.GetDropdowns();
If you want to call GetDropdowns() you can:
Instantiate Helpers and call it:
new Helpers().GetDropdowns();
Make method GetDropdowns static:
public static void GetDropdowns()
{
}
and then call it:
Helpers.GetDropdowns();
You may also inherit AdjusterController from Helpers:
public class AdjusterController : Helpers
{
}
and then call it just as you did. Everything depends on what you're interested in. I guess you should not inherit Helpers from Controller and make the method static, but it's just a guess.
You can use the Extension Methods instead inheriths from Controller:
public static class Helpers
{
public static void GetDropdowns(this Controller controller)
{
// do something with the "controller", for sample:
controller.ViewBag = new List<string>();
}
}
Everything you need to access on the controller you can do by the controller parameter.
And in your controller you can do something like this:
public ActionResult Index()
{
// just call it, and the .net framework will pass "this" controller to your extension methodo
GetDropdowns();
}

Adding item to the List<T>

I am probably doing something silly but cannot find out what. I am trying to modify simple membership functionality in ASP.NET MVC 4. I have slightly modified a RegisterModel coming with the template, and added a list of categories to it like so:
public class RegisterModel
{
...
public List<SelectListItem> Categories { get; set; }
}
Then in the account controller I'm trying to add an item to this list but get "Object reference not set to an instance of an object." error:
[AllowAnonymous]
public ActionResult Register()
{
RegisterModel rm = new RegisterModel();
//SelectListItem guestCategory = new SelectListItem();
//guestCategory.Value = null;
//guestCategory.Text = "Guest";
rm.Categories.Add(new SelectListItem { Value = null, Text = "Guest" });
...
Any ideas why?
you just need to do this before adding items to list , because when you add items its not get instantiated that why its giving error
rm.Categories = new List<SelectListItem>();
that means in this method do like this
[AllowAnonymous]
public ActionResult Register()
{
RegisterModel rm = new RegisterModel();
rm.Categories = new List<SelectListItem>();//added line
rm.Categories.Add(new SelectListItem { Value = null, Text = "Guest" });
...
or
you can do same thing in constructor of RegisterModel .
public class RegisterModel
{
public RegisterModel
{
Categories = new List<SelectListItem>();//added line
}
In your class constructor initialize the list
public class RegisterModel
{
RegisterModel()
{
Categories = new List<SelectListItem>();
}
......
Since you are using an auto-implemented property {get;set;} you have to initialize it in the constructor. If you don't want to do in the Constructor then you can do:
public class RegisterModel
{
    ...
private List<SelectListItem> _Categories = new List<SelectListItem>();
private List<SelectListItem> Categories
{
get { return _Categories; }
set { _Categories = value; }
}
}
You can also initialize the List with the object, before using it.
RegisterModel rm = new RegisterModel();
r.Categories = new List<SelectListItem>(); // like that
rm.Categories.Add(new SelectListItem { Value = null, Text = "Guest" });
But its better if you initialize the list in constructor or through a private field (if not using auto-implemented property), Because then you don't have to initialize a property of the object of Class RegisterModel, with each object creation.
You never initialized Categories to anything. It is null.
Initialize it to an empty list to avoid the error, preferably in the constructor:
Categories = new List<SelectListItem>();

Set selected value in dropdown list

How do I set the selected value on a drop down list? Here is what I have so far:
#model Web.Models.PostGraduateModels.PlannedSpecialty
#Html.DropDownList("PlannedSpecialtyID")
//controller
[HttpGet]
public PartialViewResult PlannedSpecialty()
{
// Get Planned Specialty ID
var pgtservice = new PgtService();
PostGraduateModels.PlannedSpecialty plannedSpecialty = pgtservice.GetPlannedSpecialtyId();
// Get Data for Planned Specialty DropDown List from SpecialtyLookup
var pgtServ = new PgtService();
var items = pgtServ.GetPlannedSpecialtyDropDownItems();
ViewBag.PlannedSpecialtyId = items;
return PartialView(plannedSpecialty);
}
// service
public IEnumerable<SelectListItem> GetPlannedSpecialtyDropDownItems ()
{
using (var db = Step3Provider.CreateInstance())
{
var specialtyList = db.GetPlannedSpecialtyDdlItems();
return specialtyList;
}
}
// data access
public IEnumerable<SelectListItem> GetPlannedSpecialtyDdlItems()
{
IEnumerable<Specialty> specialties = this._context.Specialties().GetAll();
var selList = new List<SelectListItem>();
foreach (var item in specialties)
{
var tempps = new SelectListItem()
{
Text = item.Description,
Value = item.Id.ToString()
};
selList.Add(tempps);
}
return selList;
}
I would recommend you to avoid using ViewBag/ViewData/ Weekly typed code. Use strongly typed code and it makes it more readable. Do not use the Magic strings/ Magic variables. I would add a collection property to your ViewModel to hold the SelectList items and another property to hold the selected item value.
public class PlannedSpecialty
{
public IEnumerable<SelectListItem> SpecialtyItems { set;get;}
public int SelectedSpeciality { set;get;}
//Other Properties
}
and in your Get action, If you want to set some Item as selected,
public PartialViewResult PlannedSpecialty()
{
var pgtServ = new PgtService();
var vm=new PlannedSpecialty();
vm.SpecialtyItems = pgtServ.GetPlannedSpecialtyDropDownItems();
//just hard coding for demo. you may get the value from some source.
vm.SelectedSpeciality=25;// here you are setting the selected value.
return View(vm);
}
Now in the View, use the Html.DropDownListFor helper method
#Html.DropDownListFor(x=>x.SelectedSpeciality,Model.SpecialtyItems,"select one ")
Use the selected property of the SelectListItem class:
selList.Selected = true;

Categories