I have been having an issue with updating a nullable bool value using TryUpdateModel. I have a template created to handle the values as so:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.Boolean?>" %>
<% if (ViewData.ModelMetadata.IsNullableValueType) { %>
<%= Html.DropDownListFor(model => model, new SelectListItem[] { new SelectListItem() { Text = "", Value = "null"},new SelectListItem() { Text = "Yes", Value = "true"}, new SelectListItem() { Text = "No", Value = "false" }})%>
<% } else { %>
<%= Html.CheckBoxFor(model => model.Value)%>
<% } %>
My View looks like this:
<%=Html.EditorFor(model => model.TestField) %> //which looks/acts correctly
The SQL Server Database types are also defined correctly as a nullable bit.
My Code is straight forward:
var so = new SomeObject();
if (ModelState.IsValid)
{
//gets to here
if (TryUpdateModel(so))
{
//never gets here
}
}
The Error reported for ModelState on that field is: "The value 'null' is not valid for TestField."
This seems pretty straight forward, but I wasn't able to find anything on this. Any help would be greatly appreciated.
Cheers,
Brian
Since nobody has answered my question, I will put my workaround up. It's not super elegant, but it works. If I wanted it to be pretty, it'd be in a pink font. ;)
Basically I had to load "so" (someObject) manually using the form Collection like so...
var so = new SomeObject();
if (ModelState.IsValid)
{
so.WasItFound = StringToNullBool(form["WasItFound"]);
so.WhereWasItFound = form["WhereWasItFound"];
//fill in the rest of the properties using the form Collection...
}
private bool? StringToNullBool(string s)
{
if (s != "null")
return Convert.ToBoolean(s);
else
return null;
}
Related
I have Industry type in my database. When it is null, it is showing an error like:
"Nullable object must have a value."
I want my value to display empty when it is null too.
This is my code:
<p>
<strong>Industry Type:</strong>
<%: Model.GetIndustry(Model.IndustryId.Value).Name%>
</p>
Anyone have an idea? Please help me...
It is similar to "von v"'s answer, but I think the error of "Nullable object must have a value." can be from IndustryId since is nullable, so it is good to check for that first:
<p>
<strong>Industry Type:</strong>
<%:if(Model.IndustryId.HasValue)
{
var idustry = Model.GetIndustry(Model.IndustryId.Value);
if(industry!= null)
industry.Name
}
else
{
""
}
%>
</p>
In my view it is good to do this
Model.GetIndustry()
in Back end, like controller, and then return the industry by viewstate,
like checking in the:
string industryName = "";
if(Industry.IndustryId.HasValue){
var industry = YourClass.GetIndustry(Industry.IndustryId.Value);
industryName = industry!= null ? Industry.Name : "" ;
}
ViewBag.IndustryName= industryName;
and then use your ViewBag in view like this:
<p>
<strong>Industry Type:</strong>
<%: ViewBag.IndustryName %>
</p>
It is good to separate the checking from view and do the logics in code.
Hope it helps.
Check that an industry is returned first then write the name if there's any
<%
var industry = Model.GetIndustry(Model.IndustryId.Value);
%>
<%: industry == null ? "" : industry.Name %>
Is IndustryId nullable? Then you can do this
<%
var industry = Model.GetIndustry(Model.IndustryId.GetValueOrDefault(0));
%>
and in the GetIndustry method, you can just return null if the id is zero.
public Industry GetIndustry(int id) {
if (id==0) return null;
// else do your stuff here and return a valid industry
}
string can accept null or empty value, why don't you try string.IsNullOrEmpty(Model.IndustryId.Value)
this is pretty self explanatory. in your controller,
string yourValue = string.empty;
yourValue = Model.GetIndustry(Model.IndustryId.Value).Name;
if(string.IsNullOrEmpty(yourValue))
{
//display your result here!
}
ViewBag.IndustryValue = yourValue;
Please try something along the lines of
<p>
<strong>Industry Type:</strong>
<%: Model.IndustryId.HasValue ? Model.GetIndustry(Model.IndustryId.Value).Name : string.Empty%>
</p>
I found the Problem.
<p>
<strong>Industry Type:</strong>
<% if (Model.IndustryId.HasValue)
{ %>
<%: Model.GetIndustry(Model.IndustryId.Value).Name%>
<%}
else
{ %>
<%:Model.IndustryId==null ? "": Model.GetIndustry(Model.IndustryId.Value).Name %>
<%} %>
</p>
I'm trying to display/return a url using a viewbag. I get this on the Action as the value of the ViewBag:
http:\\www.mysite.com\\images\\logo.jpg
But whenever the view gets loaded all the slash gets removed and would end up with this:
http:wwwmysitecomimageslogo.jpg
EDIT: For Code on generating the LogoUrl.
EDIT 2: This is how I pull it out of the db, I use context and pull it out using EF, how else do you pull it out?
ViewBag.LogoUrl = context.Event.FirstOrDefault(e => e.Id == id).LogoUrl;
This is how I know what gets pulled:
string url = context.Event.FirstOrDefault(e => e.Id == id).LogoUrl;
ViewBag.LogoUrl = url;
This is how I use it:
var logoUrl;
$('#logo').click(function (event) {
<% if(ViewBag.LogoUrl != null) { %>
logoUrl = '<%: ViewBag.LogoUrl %>';
<% } %>
:
:
:
return false;
}
Make sense?
I'm using ASP.NET MVC 3, and just ran into a 'gotcha' using the DropDownListFor HTML Helper.
I do this in my Controller:
ViewBag.ShippingTypes = this.SelectListDataRepository.GetShippingTypes();
And the GetShippingTypes method:
public SelectList GetShippingTypes()
{
List<ShippingTypeDto> shippingTypes = this._orderService.GetShippingTypes();
return new SelectList(shippingTypes, "Id", "Name");
}
The reason I put it in the ViewBag and not in the model (I have strongly typed models for each view), is that I have a collection of items that renders using an EditorTemplate, which also needs to access the ShippingTypes select list.
Otherwise I need to loop through the entire collection, and assign a ShippingTypes property then.
So far so good.
In my view, I do this:
#Html.DropDownListFor(m => m.RequiredShippingTypeId, ViewBag.ShippingTypes as SelectList)
(RequiredShippingTypeId is of type Int32)
What happens is, that the value of RequiredShippingTypeId is not selected in the drop down.
I came across this: http://web.archive.org/web/20090628135923/http://blog.benhartonline.com/post/2008/11/24/ASPNET-MVC-SelectList-selectedValue-Gotcha.aspx
He suggests that MVC will lookup the selected value from ViewData, when the select list is from ViewData. I'm not sure this is the case anymore, since the blog post is old and he's talking about MVC 1 beta.
A workaround that solves this issue is this:
#Html.DropDownListFor(m => m.RequiredShippingTypeId, new SelectList(ViewBag.ShippingTypes as IEnumerable<SelectListItem>, "Value", "Text", Model.RequiredShippingTypeId.ToString()))
I tried not to ToString on RequiredShippingTypeId at the end, which gives me the same behavior as before: No item selected.
I'm thinking this is a datatype issue. Ultimately, the HTML helper is comparing strings (in the Select List) with the Int32 (from the RequiredShippingTypeId).
But why does it not work when putting the SelectList in the ViewBag -- when it works perfectly when adding it to a model, and doing this inside the view:
#Html.DropDownListFor(m => m.Product.RequiredShippingTypeId, Model.ShippingTypes)
The reason why this doesn't work is because of a limitation of the DropDownListFor helper: it is able to infer the selected value using the lambda expression passed as first argument only if this lambda expression is a simple property access expression. For example this doesn't work with array indexer access expressions which is your case because of the editor template.
You basically have (excluding the editor template):
#Html.DropDownListFor(
m => m.ShippingTypes[i].RequiredShippingTypeId,
ViewBag.ShippingTypes as IEnumerable<SelectListItem>
)
The following is not supported: m => m.ShippingTypes[i].RequiredShippingTypeId. It works only with simple property access expressions but not with indexed collection access.
The workaround you have found is the correct way to solve this problem, by explicitly passing the selected value when building the SelectList.
This might be silly, but does adding it to a variable in your view do anything?
var shippingTypes = ViewBag.ShippingTypes;
#Html.DropDownListFor(m => m.Product.RequiredShippingTypeId, shippingTypes)
you can create dynamic viewdata instead of viewbag for each dropdownlist field for complex type.
hope this will give you hint how to do that
#if (Model.Exchange != null)
{
for (int i = 0; i < Model.Exchange.Count; i++)
{
<tr>
#Html.HiddenFor(model => model.Exchange[i].companyExchangeDtlsId)
<td>
#Html.DropDownListFor(model => model.Exchange[i].categoryDetailsId, ViewData["Exchange" + i] as SelectList, " Select category", new { #id = "ddlexchange", #class = "form-control custom-form-control required" })
#Html.ValidationMessageFor(model => model.Exchange[i].categoryDetailsId, "", new { #class = "text-danger" })
</td>
<td>
#Html.TextAreaFor(model => model.Exchange[i].Address, new { #class = "form-control custom-form-control", #style = "margin:5px;display:inline" })
#Html.ValidationMessageFor(model => model.Exchange[i].Address, "", new { #class = "text-danger" })
</td>
</tr>
}
}
ViewModel CompanyDetail = companyDetailService.GetCompanyDetails(id);
if (CompanyDetail.Exchange != null)
for (int i = 0; i < CompanyDetail.Exchange.Count; i++)
{
ViewData["Exchange" + i]= new SelectList(companyDetailService.GetComapnyExchange(), "categoryDetailsId", "LOV", CompanyDetail.Exchange[i].categoryDetailsId);
}
I was just hit by this limitation and figured out a simple workaround. Just defined extension method that internally generates SelectList with correct selected item.
public static class HtmlHelperExtensions
{
public static MvcHtmlString DropDownListForEx<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
object htmlAttributes = null)
{
var selectedValue = expression.Compile().Invoke(htmlHelper.ViewData.Model);
var selectListCopy = new SelectList(selectList.ToList(), nameof(SelectListItem.Value), nameof(SelectListItem.Text), selectedValue);
return htmlHelper.DropDownListFor(expression, selectListCopy, htmlAttributes);
}
}
The best thing is that this extension can be used the same way as original DropDownListFor:
#for(var i = 0; i < Model.Items.Count(); i++)
{
#Html.DropDownListForEx(x => x.Items[i].CountryId, Model.AllCountries)
}
There is an overloaded method for #html.DropdownList for to handle this.
There is an alternative to set the selected value on the HTML Dropdown List.
#Html.DropDownListFor(m => m.Section[b].State,
new SelectList(Model.StatesDropdown, "value", "text", Model.Section[b].State))
I was able to get the selected value from the model.
"value", "text", Model.Section[b].State this section the above syntax adds the selected attribute to the value loaded from the Controller
I am using the telerik MVC template and have a database that has a huge number of columns and the telerik grid does not have a horizontal scroll bar, so I created checkboxes for the user to select exactly what columns they want to view. It works well enough in that when I first go to the page it shows the checkboxes at top with the Apply button and the grid view underneath. Because nothing has been submitted from the WebForm, the grid view shows all the columns. Before adding the cookies, the user only had to press apply once for only those columns to appear. However, if the user then tried to sort or filter one of these columns, it would revert back to showing all of the columns. So, I created a cookie to store the selected information. Unfortunately, this only helped with the selection of the first filter. If a second filter is used, it would, again, show all of the columns instead of just the ones selected. Furthermore, the user now has to press apply twice for their selections to show properly on the grid view. Here is a brief explanation of how I have everything coded:
Index View
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm("Index", "Order"))
{ %>
<p>
<%= Html.CheckBox("osurr", true, "Ad Number")%>Ad Number //I set this and a few other columns to default to true
<%= Html.CheckBox("csurr", false, "Customer Number")%>Customer Number
<%= Html.CheckBox("rhosurr", false, "RHO Number")%>RHO Number
<%= Html.CheckBox("lockid", false, "Lock ID")%>Lock ID
//And several more
</p>
<input type="submit" value="Apply" />
<% } %>
<%
Html.Telerik().Grid(Model)
.Name("Grid")
.Columns(columns =>
{
columns.Template(o =>
{
%>
<%=Html.ActionLink("Detail", "Edit", new { id = o.osurr })%>
<%
}).Width(25);
if (Request.Cookies["DBCols"]["csurr"] != null)
{
if (Request.Cookies["DBCols"].Values["csurr"].Equals("True")) { columns.Bound(o => o.csurr).Title("Cust. No."); }
}
if (Request.Cookies["DBCols"]["rhosurr"] != null)
{
if (Request.Cookies["DBCols"].Values["rhosurr"].Equals("True")) { columns.Bound(o => o.rhosurr).Title("RHO No."); }
}
if (Request.Cookies["DBCols"]["lockid"] != null)
{
if (Request.Cookies["DBCols"].Values["lockid"].Equals("True")) { columns.Bound(o => o.lockid).Title("Lock ID"); }
}
//And again, several more.
})
.Groupable(grouping => grouping.Enabled(true))
.Resizable(resizing => resizing.Columns(true))
.Filterable(filter => filter.Enabled(true))
.Sortable(sorting => sorting.Enabled(true))
.Pageable(paging => paging.Enabled(true).PageSize(25))
.Render();
%>
</asp:Content>
Controller
public ActionResult Index(bool? csurr, bool? rhosurr, bool? lockid /* And Several More */)
{
ViewData["csurr"] = csurr ?? true;
ViewData["rhosurr"] = rhosurr ?? true;
ViewData["lockid"] = lockid ?? true;
if ((bool)ViewData["csurr"]) { DBCols.Values["csurr"] = (ViewData["csurr"].ToString());
}
else { DBCols.Values["csurr"] = "False"; }
if ((bool)ViewData["rhosurr"]) { DBCols.Values["rhosurr"] = (ViewData["rhosurr"].ToString()); }
else { DBCols.Values["rhosurr"] = "False"; }
if ((bool)ViewData["lockid"]) { DBCols.Values["lockid"] = (ViewData["lockid"].ToString()); }
else { DBCols.Values["lockid"] = "False"; }
//And Several more
var db = new MillieOrderDB();
var listView = from m in db.vw_cadords
orderby m.createstamp descending
select m;
return View(listView);
}
I am working just in the Index ActionResult for now to keep things in one place while I figure out how to get this all to work. Anyone have any ideas why I am having to press apply twice, why I can not use more than one filter, and how to avoid this?
It turns out that the reason I had to hit apply twice and why when applying more than one filter caused issues was because I had everything in the Index ActionResult. Once I moved all the form data to its own ActionResult and then did RedirecttoAction("Index"), everything worked fine!
The problem is: when I put 2 controls of the same type on a page I need to specify different prefixes for binding. In this case the validation rules generated right after the form are incorrect. So how to get client validation work for the case?:
the page contains:
<%
Html.RenderPartial(ViewLocations.Shared.PhoneEditPartial, new PhoneViewModel { Phone = person.PhonePhone, Prefix = "PhonePhone" });
Html.RenderPartial(ViewLocations.Shared.PhoneEditPartial, new PhoneViewModel { Phone = person.FaxPhone, Prefix = "FaxPhone" });
%>
the control ViewUserControl<PhoneViewModel>:
<%= Html.TextBox(Model.GetPrefixed("CountryCode"), Model.Phone.CountryCode) %>
<%= Html.ValidationMessage("Phone.CountryCode", new { id = Model.GetPrefixed("CountryCode"), name = Model.GetPrefixed("CountryCode") })%>
where Model.GetPrefixed("CountryCode") just returns "FaxPhone.CountryCode" or "PhonePhone.CountryCode" depending on prefix
And here is the validation rules generated after the form. They are duplicated for the field name "Phone.CountryCode". While the desired result is 2 rules (required, number) for each of the FieldNames "FaxPhone.CountryCode", "PhonePhone.CountryCode"
alt text http://www.freeimagehosting.net/uploads/37fbe720bf.png
The question is somewhat duplicate of Asp.Net MVC2 Clientside Validation and duplicate ID's problem
but the advise to manually generate ids doesn't helps.
Correct way to set the same prefixes both for textbox and validation:
<% using (Html.BeginHtmlFieldPrefixScope(Model.Prefix)) { %>
<%= Html.TextBoxFor(m => m.Address.PostCode) %>
<%= Html.ValidationMessageFor(m => m.Address.PostCode) %>
<% } %>
where
public static class HtmlPrefixScopeExtensions
{
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private class HtmlFieldPrefixScope : IDisposable
{
private readonly TemplateInfo templateInfo;
private readonly string previousHtmlFieldPrefix;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
this.templateInfo = templateInfo;
previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
public void Dispose()
{
templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
}
}
}
(by chance found the solution in the code on Steve Sanderson's blog http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/)
Also looks like Html.EditorFor approach should work as well as suggested here: ASP.NET MVC 2 - ViewModel Prefix