Control isn't getting the Selected value from DropDownListFor - asp.net

I got the following Model:
public class ViewBloqueioNotaFiscal
{
public ViewComboStatus ComboStatus = new ViewComboStatus();
public class ViewComboStatus
{
public int? IdStatusSelecionado { get; set; }
public IEnumerable<SelectListItem> ComboStatus { get; set; }
}
}
The following controller method:
public ViewBloqueioNotaFiscal.ViewComboStatus geraComboStatus(int? statusSelecionado)
{
ViewBloqueioNotaFiscal.ViewComboStatus combo = new ViewBloqueioNotaFiscal.ViewComboStatus
{
IdStatusSelecionado = statusSelecionado,
ComboStatus = new[]{
new SelectListItem { Value = 1, Text = "Op1"},
new SelectListItem { Value = 2, Text = "Op2"}
}
};
return combo;
}
And my aspx is like:
<%: Html.DropDownListFor(x => x.ComboStatus.IdStatusSelecionado, Model.ComboStatus.ComboStatus) %>
Its getting perfectly displayed for selection but when I submit my form, my post method from controller gets the model perfectly with the values except for this combo that Im recieving null value into the model. As its the first one that I try, I think that something is wrong.
Could you guys check that for me? If you have any better solution for this I d like to know too.
thanks for the help !

You are not binding to the correct property of your view model. You are binding to some complex object (ComboStatus) which doesn't make sense.
You should bind the drop down list to the IdStatusSelecionado property:
<%: Html.DropDownListFor(
x => x.ComboStatus.IdStatusSelecionado,
Model.ComboStatus.ComboStatus
) %>
A strongly typed DropDownListFor helper requires at least 2 things on your view model:
A scalar property (int, decimal, string, ...) which will be used to bind to
A collection of value/text pairs.
If the collection of value/text pairs contains an item whose value is equal to the scalar property you used as first argument, this item will be preselected. For example if you wanted to preselect the second item in your example you would set IdStatusSelecionado=2 on your view model.
Side note: Model.ComboStatus.ComboStatus looks terrible. Please rename.

Related

Unable to Set DropDownlist with pre-selected value

Unable to populate dropdonwlist with pre-selected value.Populating with pre-selected value works with viewbag method.However when i am trying the same with a Model based method i find no luck.
public ActionResult Edit(int?id)
{
Job_ClientVM Vm = new Job_ClientVM();
Vm.Job_Info = Get_jobdetails(id);
//4 is desired pre-selected ID value
Vm.Recruiters = new SelectList(db.Recruiter_Info.ToList(), "Id",
"Recruiter_Name",4);
}
Here is my View
#Html.DropDownListFor(Model => Model.SelectedRecruiterID, Model.Recruiters, "Assign Recruiter", new { #class = "form-control" })
You need to set the value of SelectedRecruiterID in the GET method before you pass the model to the view.
public ActionResult Edit(int?id)
{
Job_ClientVM Vm = new Job_ClientVM();
Vm.Job_Info = Get_jobdetails(id);
Vm.Recruiters = new SelectList(db.Recruiter_Info.ToList(), "Id", "Recruiter_Name");
// Set the value of SelectedRecruiterID
Vm.SelectedRecruiterID = 4;
// Pass the model to the view
return View(Vm);
}
Note that setting the Selected property (the 4th parameter) in the SelectList constructor is pointless - its ignored when binding to a model property (internally the method builds a new IEnumerable<SelectListItem> and sets the Selected property based on the value of the property.

Html.DropDownList default selection troubleshoot

I have a selection of names, drawn from my database, to which I prepend the option "None" in my Controller:
var rtrnStaff = (from st in db.PrmTbl_Staffs
join sal in db.PrmTbl_Salutations on st.SalutationID equals sal.ID
where st.Active == true
select new { st.ID, Name = sal.Desc + ". " + st.Name });
List<SelectListItem> staff = new SelectList(rtrnStaff, "ID", "Name").ToList();
staff.Insert(0, (new SelectListItem { Text = "None", Value = "0" }));
ViewData["Staff"] = staff;
I then present this list as a dropdown multiple times in my View, where each time I feed it a variable containing the ID of the desired default option:
#Html.DropDownList(thisSelectID, (IEnumerable<SelectListItem>)ViewData["Staff"], thisStaffID)
Using breakpoints, I can see that these variables are being filled correctly (eg. "3", "2", etc.), but in every case the option shown is the first one: "None". Where is my error?
When you use DropDownList you don't have any way to set the selected value but in the list itself. None of the overloads allows to do it.
So, if you only have one list, it's unavoidable to have all the controls with the same selected item.
Most times it's much better, and make the code much more clear to use a view model class for your view, and use DropDownListFor.
When you use DropDowListFor you set the default property value in the property of the view model, and you don't have to do anything special to set the selected value.
I.e. create a class like this:
public class MyViewModel
{
public List<SelectListItem> Staff { get; set; }
public int FirstId { get; set; }
public int SecondId { get; set; }
}
Then, in your view,
#model MyViewModel
...
#Html.DropDownListFor(m => m.FirstId, Model.Staff);
#Html.DropDownListFor(m => m.SecondId, Model.Staff);
Create an instance of MyViewModel, intialize the list and the Ids, and pass it to the View
MyViewModel model = new MyViewModel
{
// Initialize here
};
...
return View("ViewName", model);
There are additional advantages: you can use the same view model class as the parameter for the POST action method, and the properties are automatically bound to the corresponding properties, you have Intellisense and a typed view is safer to implement.

How to customize the EditorFor CSS with razor

I have this class
public class Contact
{
public int Id { get; set; }
public string ContaSurname { get; set; }
public string ContaFirstname { get; set; }
// and other properties...
}
And I want to create a form that allo me to edit all those fields. So I used this code
<h2>Contact Record</h2>
#Html.EditorFor(c => Model.Contact)
This works fine, but I want to customize how the elements are displayed. For instance I want each field to be displayed in the same line as its label. Because now, the generated html is like this :
<div class="editor-label">
<label for="Contact_ContaId">ContaId</label>
</div>
<div class="editor-field">
<input id="Contact_ContaId" class="text-box single-line" type="text" value="108" name="Contact.ContaId">
</div>
I agree to the solution of jrummell above:
When you use the EditorFor-Extension, you have to write a custom
editor template to describe the visual components.
In some cases, I think it is a bit stiff to use an editor template for
several model properties with the same datatype. In my case, I want to use decimal currency values in my model which should be displayed as a formatted string. I want to style these properties using corresponding CSS classes in my views.
I have seen other implementations, where the HTML-Parameters have been appended to the properties using annotations in the Model. This is bad in my opinion, because view information, like CSS definitions should be set in the view and not in a data model.
Therefore I'm working on another solution:
My model contains a decimal? property, which I want to use as a currency field.
The Problem is, that I want to use the datatype decimal? in the model, but display
the decimal value in the view as formatted string using a format mask (e.g. "42,13 €").
Here is my model definition:
[DataType(DataType.Currency), DisplayFormat(DataFormatString = "{0:C2}", ApplyFormatInEditMode = true)]
public decimal? Price { get; set; }
Format mask 0:C2 formats the decimal with 2 decimal places. The ApplyFormatInEditMode is important,
if you want to use this property to fill a editable textfield in the view. So I set it to true, because in my case I want to put it into a textfield.
Normally you have to use the EditorFor-Extension in the view like this:
<%: Html.EditorFor(x => x.Price) %>
The Problem:
I cannot append CSS classes here, as I can do it using Html.TextBoxFor for example.
To provide own CSS classes (or other HTML attributes, like tabindex, or readonly) with the EditorFor-Extension is to write an custom HTML-Helper,
like Html.CurrencyEditorFor. Here is the implementation:
public static MvcHtmlString CurrencyEditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, Object htmlAttributes)
{
TagBuilder tb = new TagBuilder("input");
// We invoke the original EditorFor-Helper
MvcHtmlString baseHtml = EditorExtensions.EditorFor<TModel, TValue>(html, expression);
// Parse the HTML base string, to refurbish the CSS classes
string basestring = baseHtml.ToHtmlString();
HtmlDocument document = new HtmlDocument();
document.LoadHtml(basestring);
HtmlAttributeCollection originalAttributes = document.DocumentNode.FirstChild.Attributes;
foreach(HtmlAttribute attr in originalAttributes) {
if(attr.Name != "class") {
tb.MergeAttribute(attr.Name, attr.Value);
}
}
// Add the HTML attributes and CSS class from the View
IDictionary<string, object> additionalAttributes = (IDictionary<string, object>) HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
foreach(KeyValuePair<string, object> attribute in additionalAttributes) {
if(attribute.Key == "class") {
tb.AddCssClass(attribute.Value.ToString());
} else {
tb.MergeAttribute(attribute.Key, attribute.Value.ToString());
}
}
return MvcHtmlString.Create(HttpUtility.HtmlDecode(tb.ToString(TagRenderMode.SelfClosing)));
}
The idea is to use the original EditorFor-Extension to produce the HTML-Code and to parse this HTML output string to replace the created
CSS Html-Attribute with our own CSS classes and append other additional HTML attributes. For the HTML parsing, I use the HtmlAgilityPack (use google).
In the View you can use this helper like this (don't forget to put the corresponding namespace into the web.config in your view-directory!):
<%: Html.CurrencyEditorFor(x => x.Price, new { #class = "mypricestyles", #readonly = "readonly", #tabindex = "-1" }) %>
Using this helper, your currency value should be displayed well in the view.
If you want to post your view (form), then normally all model properties will be sent to your controller's action method.
In our case a string formatted decimal value will be submitted, which will be processed by the ASP.NET MVC internal model binding class.
Because this model binder expects a decimal?-value, but gets a string formatted value, an exception will be thrown. So we have to
convert the formatted string back to it's decimal? - representation. Therefore an own ModelBinder-Implementation is necessary, which
converts currency decimal values back to default decimal values ("42,13 €" => "42.13").
Here is an implementation of such a model binder:
public class DecimalModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object o = null;
decimal value;
var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var modelState = new ModelState { Value = valueResult };
try {
if(bindingContext.ModelMetadata.DataTypeName == DataType.Currency.ToString()) {
if(decimal.TryParse(valueResult.AttemptedValue, NumberStyles.Currency, null, out value)) {
o = value;
}
} else {
o = Convert.ToDecimal(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
}
} catch(FormatException e) {
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return o;
}
}
The binder has to be registered in the global.asax file of your application:
protected void Application_Start()
{
...
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new DecimalModelBinder());
...
}
Maybe the solution will help someone.
Create a partial view called Contact.cshtml with your custom markup in Views/Shared/EditorTemplates. This will override the default editor.
As noted by #smartcavemen, see Brad Wilson's blog for an introduction to templates.

HTML.DropDownList values from multiple sources?

In ASP.NET MVC, is it possible to fill the list of values of a Html.DropDownList from multiple data sources along with multiple manually entered values?
Basically, I envision it being formated like the below using something along the lines of OPTGROUP:
**Group 1**
Manual Item 1
Manual Item 2
**Group 2**
DS1 Item 1
DS1 Item 2
**Group 3**
DS2 Item 1
DS2 Item 2
I've thought about using a view on the DB and getting the data from that, however, I've not really the faintest how to lay it out like above using helpers and to pass the data to it from multiple sources.
Thanks for any help in advance.
As always start with a model (actually start with a unit test but no time for this here):
public class MyModel
{
public string SelectedItem { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
}
Then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var items1 = new[]
{
new { Value = "1", Text = "Manual Item 1" },
new { Value = "2", Text = "Manual Item 2" },
};
// TODO: Go fetch those from your repo1
var items2 = new[]
{
new { Value = "3", Text = "DS1 Item 1" },
new { Value = "4", Text = "DS1 Item 2" },
};
// TODO: Go fetch those from your repo2
var items3 = new[]
{
new { Value = "5", Text = "DS2 Item 1" },
new { Value = "6", Text = "DS2 Item 2" },
};
var items = items1.Concat(items2).Concat(items3);
var model = new MyModel
{
Items = new SelectList(items, "Value", "Text")
};
return View(model);
}
}
And finally a strongly typed view to the model:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyApp.Models.MyModel>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<%= Html.DropDownListFor(x => x.SelectedItem, Model.Items) %>
</asp:Content>
You will probably define an intermediary type to avoid the anonymous types that I've used for brevity.
Remark: If your original question was about using an OPTGROUP then ignore my answer and make your intention clear so that you can get a more adapted answer.
It seems it would be easier for you to write your own helper. The basic syntax to do that is this:
// The class can be named anything, but must be static and accessible
public static class HtmlHelperExtensions
{
// The method name is what you want to call on Html,
// in this case Html.CoolSelectList(arguments...)
//
// The method has to be static, and the first argument should be of the type
// you're extending (in this case HtmlHelper, which is the type of the
// Html property on your view). The first argument must be prefixed with the
// "this" keyword, to indicate it's an extension method.
//
// All the following arguments will be arguments that you supply when calling
public static string CoolSelectList(this HtmlHelper helper,
IEnumerable<IEnumerable<CoolThingThatWeMakeAListOf>> groups)
{
// I chose an argument of type IEnumerable<IEnumerable<T>>, since that
// allows you to create each group of item on its own (i.e. get them from
// various data sources) and then add all of them to a list of groups
// that you supply as argument. It is then easy to keep track of which
// items belong to which groups, etc.
// Returned from the extension method is a string you have built, that
// constitutes the html you want to output on your view. I usually use
// the TagBuilder class to build the html.
return "this is what will be on the page";
}
}
Many solutions exist for you problem. One would be the one that Tomas described, another is a Controller Action that returns PartialView, which contains the code to render the input and option tags, another solution would be to have the Controller Action populate the ViewData with a SelectList or have the SelectList as a strong type for your View/ViewUserControl (Partial).

Asp.Net Gridview - One Column is List<string> - Want to Show Only The Last Item

I have an Asp.Net GridView. One of the Columns is a List, but I only want to show the Last Item in the list. How can I do this?
List<string> Column1
I am binding the Gridview to a business object:
public Class GridObject
{
List<string> Column1 { get; set; }
}
EDIT
This worked, but is it the best solution:
<%# ((List<string>)Eval("Column1"))[((List<string>)Eval("Column1")).Count - 1] %>
I would add a property to the object you are binding to, and use that property instead of the list property in your binding.
public Class GridObject
{
List<string> Column1 { get; set; }
public string Column1LastValue
{
get
{ // return Column1.Last(); if linq is available
return Column1[Column1.Count-1];
}
}
}
Edit: Adding a presentation wrapper allows you to unit test what will be displayed. You are doing a translation in the view, which is OK, but since you technically have some logic happening to translate your business object to something proper for display, you would likely want to unit test that translation. Then, any formatting you want to apply to any of your business object fields is wrapped in a testable class, rather than hidden on the untestable view. Here is a sample of how this could be done:
public class GridObjectView
{
private GridObject _gridObject;
public GridObjectView(GridObject gridObject)
{
_gridObject = gridObject;
}
public string Column1
{
get
{
return _gridObject.Column1.Last();
}
}
}
Then to do the databinding, you could do this:
List<GridObject> data = GetGridData();
grid.DataSource = data.Select(g => new GridObjectView(g));
grid.DataBind();
Your best bet is to create a template column and use an inline script to retrieve the value from the list:
<%= ((List<string>)DataBinder.Eval("Column1"))[((List<string>)DataBinder.Eval("Column1")).Count] %>
Or you could store the result in the text of a label or a literal.
Hope that helps

Resources