Using nullable bool in HTML helper in ASP.NET MVC app - c#

I have an ASP.NET MVC app. I need to create an HTML helper to generate a custom Checkbox field. I have the Checkbox field working with bool values. However, I'm not sure how to do it with nullable bool values (bool?). Currently, I have the following extension method:
public static MvcHtmlString CreateCheckbox<TModel>(this HtmlHelper<TModel> html, Expression<Func<TModel, bool?>> expression, string classes = " ")
{
var str = System.Web.Mvc.Html.InputExtensions.CheckBoxFor(html, expression).ToString();
var s= ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var html = string.Format("<label for=\"{0}\">{1} {2}</label>", s.PropertyName, str, s.GetDisplayName());
return new MvcHtmlString(html);
}
When I compile it, I get the following error:
Argument 2: cannot convert from 'System.Linq.Expressions.Expression<System.Func<TModel,bool?>>' to 'System.Linq.Expressions.Expression<System.Func<TModel,bool>>'
How can I accept nullable bool values in this HTML helper?
Thank you!

HTML5 defines a property for checkboxes called indeterminate. Unfortunately, it cannot be set with markup, only with JavaScript. And it doesn't change the value of checkbox, it's just a visual thing. Not all of the browsers have support for them. I'd highly encourage to use dropdown list for that case. So the user won't be intimidated by third-party custom checkboxes with 3 states. Moreover, dropdown list will work on all devices and browsers.

Related

Working of HTML/AJAX Helpers in .Net MVC

I think this might be pretty obvious for others but I cannot understand the working of these type of method overloads.
The ones in which we only pass action method name.
How does these #Html.Helpers get the expected controller?
My guesses:
That particular Helper extract it from the HttpContext
this HtmlHelper htmlHelper parameter holds all the information related
to the request.
I am trying to create a custom helper in which user should pass the name of the action method and it magically gets its respective controller.
When I checked the route-values in this HtmlHelper htmlHelper during run-time they showed me this-
{controller, Home}
{action, Index}
{id, null}
When I checked the HttpContext during run-time, it was null.
Expected output should be-
{controller, Device}
{action, Suggest}
{id, null}
If someone can provide behind the curtains working of default #Html.Helpers with examples that will be great.
UPDATE
I did find what I was looking for.
Here is the link to the question and its answer
All Html helpers have access to the HtmlHelper class. That reason is that these helpers are nothing else but extension methods of the HtmlHelper class.
public static class HelperExtensions
{
public static string ControllerName(this HtmlHelper helper)
{
string name = helper.ViewContext.Controller.GetType().Name
return String.Format("<span'>{0}</span>", name);
}
}
}
So everything regarding the current request is there.
View.cshtml
#Html.ControllerName()

How to create custom validation helper that change the corresponsing field value in MVC 4?

I'm starting to write my own field validation helper, no matter the reason.
I want to control the value in the textbox being validated and not only render a validation message.
Is there a way to access the html control being validated from my custom extension code?
Here is a very basic beginning:
(Please don't judge the code, i simplified it for the sake of the question)
public static MvcHtmlString CustomValidatioMessageFor<TModel,
TProperty>(this HtmlHelper obj, Expression<Func<TModel,TProperty>>
expression){
string html = (string)obj.ValidationMessageFor(expression);
html = "<div>" + /* Here goes the error message blabla... */ + "</div>";
return new MvcHtmlString(html);
}
I want to control the value in the textbox being validated
Validation helpers are designed to validate the data once it is input but you need to constraint the data while it is being input. This means that you cannot achieve it using validation helpers; you need a more eager approach based on hooking into keyboard/mouse events. Also, it will be completely javascript based as on server side you don't have text boxes; you only have posted models.
The ASP.net MVC framework uses jquery-validation to perform validation client side prior to errors being passed back to the server. If a Validation helper's code has been hit, the data has already been submitted off the client, and the only thing the server can do is accept or deny the processing of the data.
You can write your own handlers for fields and define different rules, see the source for jquery validation demo for multiple examples of different ways to take action on your form fields.

ASP.NET MVC 3 Razor DisplayFor Delegate

I'm getting this error:
Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
Here's my code (custom HTML helper, wrapping DisplayFor so i can choose a template):
public static string DisplayLocationTypeFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, LocationType>> expression, bool plural = false)
{
return plural ?
htmlHelper.DisplayFor(expression, "LocationTypePlural").ToHtmlString() :
htmlHelper.DisplayFor(expression).ToHtmlString();
}
When i use it like this, it works:
#Html.DisplayLocationTypeFor(model => model.LocationType)
Because the model has a property for LocationType.
But when i do this in another custom HTML helper:
public static MvcHtmlString SearchPreferenceButtonForModel<TModel>(this HtmlHelper<TModel> htmlHelper)
{
// .. other code
foreach (var property in htmlHelper.ViewData.ModelMetadata.Properties)
{
if (property.PropertyName == "LocationType")
htmlHelper.DisplayLocationTypeFor(model => ((LocationType)Enum.ToObject(typeof(LocationType), property.Model)), true);
}
}
It errors.
I can change my DisplayLocationTypeFor helper to use htmlHelper.Display instead, but i'm not sure how.
Any ideas?
What i'm trying to do, is that i have a specific way of rendering out the LocationType model, that i want to happen across the site. Internally, the template uses a resource file, and some other smarts based on the URL. In other words, there is logic - which i don't wanted repeated.
This way, all my views/templates call into this template as a standard way of rendering the LocationType.
You need to read the error message:
Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
It's telling you that only certain types of (very simple!) lambda expressions are permitted in a Razor template. If you have something more complex, you need to compute the value before you try to pass it to the template. Something like this should work:
if (property.PropertyName == "LocationType") {
LocationType locationType = (LocationType) Enum.ToObject(typeof(LocationType), property.Model));
htmlHelper.DisplayLocationTypeFor(model => locationType, true);
}
You can achieve that by composing a display template for LocationType model.
Here is an answer that says how to achieve that. In short:
Create a folder ~/Views/Shared/DisplayTemplates.
Create a view named LocationType in the new folder you created with model type LocationType.
Whenever you try a #DisplayFor(model => model.LocationType), the view you created for LocationType will be rendered.

Reducing code duplication using HTML helpers

I have a fairly simple data audit web application written with ASP MVC which effectively has two views of the same model for different purposes.
Agent view - The form filled out by the person validating information the information. Each field on the form in this view has 3 subfields:
a. Original Value - The value from the database before the call was made
b. New Value - The value provided by the person on the phone if it differs from the original.
c. Action - A general indication of what happened
QC View - The form filled out by someone who reviews the work performed in Agent view. Each field on the form in this view has 5 subfields:
a. Original Value - Same as above
b. Agent Value - The value provided in 1b above by the agent.
c. QC Value - The corrected "New Value" if the value specified by the agent is incorrect.
d. Agent Action - Same as above, except read only in this view
e. QC Action - The corrected "New Action" if improperly chosen by the agent.
The only differences between the two views are the subfields available. I'd like to be able to use a single view to represent both views since the overall structure of the pages is identical, and just use HTML helpers to handle the differences in subfields. What I have so far are 2 distinctly separate series of helpers (currently in the same class though could be separated):
// Agent controls
public static MvcHtmlString AuditControl(this HtmlHelper htmlHelper, string id, string fieldLabel, MvcHtmlString editControl, string cssClass)
public static MvcHtmlString AuditControl(this HtmlHelper htmlHelper, string id, string fieldLabel, string editControl, string cssClass)
public static MvcHtmlString AuditControl<COMPLEX>(this HtmlHelper htmlHelper, string id, string fieldLabel, string cssClass) where COMPLEX : AbstractComplex, new()
// QC controls
public static MvcHtmlString ReviewControl(this HtmlHelper htmlHelper, string id, string fieldLabel, MvcHtmlString editControl, string cssClass)
public static MvcHtmlString ReviewControl(this HtmlHelper htmlHelper, string id, string fieldLabel, string editControl, string cssClass)
public static MvcHtmlString ReviewControl<COMPLEX>(this HtmlHelper htmlHelper, string id, string fieldLabel, string cssClass) where COMPLEX : AbstractComplex, new()
Where the third implementations handle the more complex fields composed of multiple pieces of data (like Full name, Address, etc).
One possible solution that I've considered is to separate the different types of controls into different classes which implement a common interface and then pass them as type parameters to more generic HTML helpers. I think this would work but then I'd somehow need to be able to tell the view which implementation it should use to draw the view, which seems problematic because it seems to blur the line between View and Controller.
One less appealing approach that seems obvious is to pass a sort of admin flag from the controller which would be used by a generic (in logic not meaning type generic) factory helper and build the logic in it to know which series of methods to use. This would keep the model and view separate, but feels dirty because then the HTML helper would become responsible for more than just building the HTML.
Is this a reasonable situation to break the separation of concerns as designed by MVC or is there a more appropriate solution?
Since you are using MVC3, I would recommend using child actions for the sub fields:
http://haacked.com/archive/2009/11/18/aspnetmvc2-render-action.aspx
Child actions allow you to execute an action on a controller inside of your view, this would be a much cleaner approach.
I was able to implement (my interpretation of) the advice provided by #SoWeLie fairly simply. It involved creating a new Model to house a superset of the possible control properties and a new view to be drawn for each different control set (one for Audit, and one for Review). The problem with it was the resulting View API was ugly:
#Html.RenderAction("DrawControl", new { id = "ID" ... })
// Repeated for all of the overloads of DrawControl
and each Controller action contained something like:
public ActionResult DrawControl(string id, ...)
{
// FieldControl being the name of my Model
var viewModel = new FieldControl() { ID = id, ... };
if (shouldRenderAudit)
return PartialView("AuditControl", viewModel);
else
return PartialView("ReviewControl", viewModel);
I couldn't figure out how to get my generic helper to work in this scenario, and besides, I wanted to remove reduce obvious code duplication so this quickly became:
#functions {
public string DrawControl(string id, ...)
{
return Html.Render("DrawControl", new { id = "ID" });
}
// Repeated for all of the overloads of DrawControl
}
#DrawControl("ID", ...)
With the same controller action. The problem with this (ignoring the fact that the View had functions at all) was that the #functions block had to be included in any view that wanted the benefit of using them (which is currently only 2 but will soon enough balloon to 5 and who knows what my predecessor is going to do with this). I quickly reworked the code again, this time to bring back the helpers (generally keeping the views, model, and controller changes) and finally ended up with this:
View:
#(Html.DrawComplexControl<ProviderName>("id", ...))
#Html.DrawSimpleControl("id", ...)
Controller:
// One common action that is used to determine which control should be drawn
public ActionResult DrawControl(FieldControl model)
{
if (shouldRenderAudit)
return PartialView("AuditControl", model);
else
return PartialView("ReviewControl", model);
}
Helper:
public static MvcHtmlString DrawControl(this HtmlHelper htmlHelper, string id, ...)
{
var model = new FieldControl() { ID = id, ... };
return htmlHelper.Action("DrawControl", model);
}
public static MvcHtmlString DrawSimpleControl(this HtmlHelper htmlHelper, string id, ...)
{
return DrawSimpleControl(htmlHelper, id, ...);
}
public static MvcHtmlString DrawSimpleControl(this HtmlHelper htmlHelper, string id, ...)
{
// Set some defaults to simplify the API
return DrawControl(htmlHelper, id, ...);
}
public static MvcHtmlString DrawComplexControl<T>(this HtmlHelper htmlHelper, string id, ...) where T : AbstractComplex, new()
{
// Build the required controls based on `T`
return DrawControl(htmlHelper, id, ...);
}
Of course there were about a half dozen other iterations between the ones shown to help the situation, and none of them did to the extent necessary. I'm sure there are improvements to be made yet, but this is what I have so far.
Doing it this way provides a very simple API for the View to use without it having to know or care about implementation and it can satisfy all of the requirements of my pre-existing API with only minor modification (in the end at least). I'm not sure if this is what the answer intended as a result but it is functional and provides the simplicity necessary.
Hopefully my headaches will help someone else in the future.

Add css class to Html.EditorFor in MVC 2

I'm trying to add a css class to a textbox. This is what I have in my view:
<%: Html.EditorFor(m => m.StartDate) %>
I tried following the instructions at this link by making my code:
<%: Html.EditorFor(m => m.StartDate, new { #class: "datepicker" }) %>
But I get a compiler error saying:
Syntax error, ',' expected
What am I doing wrong here?
With MVC3, I kept banging my head because I couldn't get this to work. I didn't want to create a whole EditorTemplate for just adding one class.
Well, instead of using EditorFor, use TextBoxFor, with of course the equals sign like so:
#Html.TextBoxFor(m=> m.ZipCode, new { #class = "zip" })
I would HIGHLY suggest using Editor Templates. It's definitely the "right" way to style your EditorFor.
You can tell a model property to use an Editor Template in two different ways.
The first (the simplest) is to create an editor template for a certain data type - DateTime for example.
The second way to do it is to set it declaratively in your DataAnnotations by using a UIHint.
Edit
I'd also like to add that you should use the "date" type in your input field so that even when JavaScript is disabled, your user can stills see a native datepicker (only valid on modern HTML5 browsers)
<input id="meeting" type="date" value="2011-01-13"/>
I guess a quick and dirty way to do this would be in jQuery, yes?
$(document).ready(function () {
$('#StartDate').addClass('datepicker');
});
Ideally, you should use the Editor Templates. I got around this issue by using the Editor Template inside the MvcHtmlString.Create() which will let you rebuild the actual HTML code. Of course, you'll want to copy everything in the "class" section to keep the Editor Template as useful as possible.
I tried many of the suggestions above, but eventually, I settled on this, because I think it's less complicated and it lets me continue using Editor Templates:
#MvcHtmlString.Create(Html.EditorFor(m => m.StartDate).ToString().Replace("class=\"text-box single-line\"", "class=\"text-box single-line datepicker\""))
I know this is an old question but thought I could contribute so here goes. I had the same problem and wanted to avoid making Editor Templates. I just wanted a generic handle everything solution that would allow me to specify html attributes when using Html.EditorFor in a view.
I really liked CIAs answer, but I expanded on it a bit so that you can pass in any attributes you need. I created an extra Html.EditorFor method that accepts html attributes:-
public static class EditorForExtentions
{
public static MvcHtmlString EditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, Object htmlAttributes, bool extendAttributes)
{
string value = html.EditorFor(expression).ToString();
PropertyInfo[] properties = htmlAttributes.GetType().GetProperties();
foreach (PropertyInfo info in properties)
{
int index = value.ToLower().IndexOf(info.Name.ToLower() + "=");
if (index < 0)
value = value.Insert(value.Length - (value.EndsWith("/>") ? 2 : 1), info.Name.ToLower() + "=\"" + info.GetValue(htmlAttributes, null) + "\"");
else if (extendAttributes)
value = value.Insert(index + info.Name.Length + 2, info.GetValue(htmlAttributes, null) + " ");
}
return MvcHtmlString.Create(value);
}
}
You can call it in a view like this
<%=Html.EditorFor(m => m.StartDate, new { #class = "datepicker" }, true)%>
It uses the normal Html.EditorFor method to get the html string, then injects the html attributes needed.
There is no overload for EditorFor that allows you to set HtmlProperties.
(IDictionary htmlAttributes)
This link explains how to do it:
http://aspadvice.com/blogs/kiran/archive/2009/11/29/Adding-html-attributes-support-for-Templates-2D00-ASP.Net-MVC-2.0-Beta_2D00_1.aspx
I was looking for a solution to apply a style to a specific box generated by the #HTML.EditorFor helper method.
The question was regarding setting a CSS class for #HTML.EditorFor but for anyone who wants to edit the style for a single element.. you can, for example, try this:
In my block, I added a style based on the ID generated by the helper:
..
<style>
#EnrollmentInfo_Format
{
width:50px;
font: normal 100% 'Lucida Grande',Tahoma,sans-serif;
font-size: 11px;
color: #2e6e9e;
}
</style>
and then in my page (i'm doing this in a partial view):
#Html.EditorFor(e => e.EnrollmentInfo.Format)
Here's a very simple solution: Remove the double quotes from "datepicker" and retype them back into Visual Studio and it should work.
I had the same problem. I copied/pasted sample code from the web and the code had a special type of quote which caused the "," syntax problem. I know it's really not obvious.

Categories