I've this code in razor page.
#{ var countryCode = Model.CountryCode;}
#if (countryCode.Equals("CA") || countryCode.Equals("US"))
{
#Html.DropDownListFor(m => m.ProvState, Model.Provinces)
}
else
{
#Html.TextBoxFor(m => m.ProvState, Model.ProvState);
}
I wrote this helper in LocatioHelper.cshtml
#helper RenderProvince(LocationModel model)
{
var countryCode = model.CountryCode;
if (countryCode.Equals("CA") || countryCode.Equals("US"))
{
#Html.DropDownListFor(model.ProvState, model.Provinces)
}
else
{
#Html.TextBoxFor(model.ProvState, model.ProvState);
}
}
I've error that I must specify type for dropDownListFor and TextBoxFor explicitly, but on view I don't specify type.
How to move this into helper ?
As per my opinion, you can pack your custom helper methods in a static class and declare your methods as simple extension methods on HtmlHelper<T>.
public static class CustomHelper
{
public static MvcHtmlString YourCustomHelper<TModel, TValue> (
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TValue>> expression ---- )
{
//Write your custom logic here.
}
}
You can import this class in your view and use this helper method using the usual syntax of #Html.YourCustomHelper()
Hope this helps.
EDIT:
In case you are using inline helper method keep the helper method in your View only where it is being used. Otherwise try creating an external html helper method as indicated above.
No, this is not possible. You could write a normal HTML helper with #Html.TextBoxFor because that your view is strongly typed.
So you need something like:
public class HelperExtentions
{
public static MvcHtmlString DefaultRenderer<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, LocationModel model, SelectList selectList, object htmlAttributes)
{
var sb = new StringBuilder();
var countryCode = string.IsNullOrEmpty(model.CountryCode):"":model.CountryCode;
var dtp = "";
if (countryCode.Equals("CA") || countryCode.Equals("US"))
{
dtp = htmlHelper.DropDownListFor(expression, selectList, htmlAttributes);
}
else
{
dtp = htmlHelper.TextBoxFor(expression, htmlAttributes).ToHtmlString();
}
sb.AppendFormat(dtp);
return MvcHtmlString.Create(sb.ToString());
}
}
Then you can use :
#html.DefaultRenderer((m => m.ProvState, Models, newSelectList(/*some list data*/) ,new { #class = "form-control" }
Related
I am currently making a wizard in MVC (c#). But I have an if statement in my Wizard view that goes like this:
if (Model.Wizard.ClientDetails.GetStep() == Model.Wizard.CurrentStep)
{
#Html.PartialFor(x => x.Wizard.ClientDetails, "_Step");
}
else if (Model.Wizard.Preferences.GetStep() == Model.Wizard.CurrentStep)
{
#Html.PartialFor(x => x.Wizard.ClientPreferences, "_Step")
}
else if (Model.Wizard.ClientQuestions.GetStep() == Model.Wizard.CurrentStep)
{
#Html.PartialFor(x => x.Wizard.ClientQuestions, "_Step")
}
The wizards have been set up pretty generically except for this part of the view where I choose which partial to display. As you can see from the code above each if follows the same structure. The only part that changes is the Model.Wizard.**Property** part.
I wanted to try and remove this if statement so I don't have to worry about writing an if statement for each step I add to a new wizard.
I want to change the code to just something like this:
#Html.PartialFor(x => x.ExampleWizardTransaction.GetStepObject(), "_Step");
My current attempt for the GetStepObject method is as follows:
public static T GetStepObject<T>(this IWizardTransaction wizardTransaction)
where T : class, new()
{
var properties = wizardTransaction.GetType().GetProperties()
.Where(x => x.PropertyType.GetCustomAttributes(typeof(StepAttribute), true).Any());
PropertyInfo #object = properties.FirstOrDefault(x => ((StepAttribute)Attribute
.GetCustomAttribute(x.PropertyType, typeof(StepAttribute))).Step == wizardTransaction.CurrentStep);
}
The PropertyInfo #object part is correctly selecting the property info for the current step in the wizard. I need to be able to return the PropertyInfo #object PropertyInfo as its correct type with its current values and return it somehow.
Is this possible?
EDIT #1:
Existing PartialFor that works in normal scenarios.
public static MvcHtmlString PartialFor<TModel, TProperty>(
this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
var name = ExpressionHelper.GetExpressionText(expression);
var model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new TemplateInfo { HtmlFieldPrefix = name }
};
return helper.Partial(partialViewName, model, viewData);
}
EDIT #2:
The reason the values are not getting binded is that the var name = ExpressionHelper.GetExpressionText(expression); part is returning a blank string. If I hard code the name variable to the actual property then the binding works. For example:
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
var compiled = expression.Compile();
var result = compiled.Invoke(helper.ViewData.Model);
var name = ExpressionHelper.GetExpressionText(expression);
//Should be ExampleWizardTransaction.ClientDetails for this step but is blank
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new TemplateInfo
{
//HtmlFieldPrefix = name
HtmlFieldPrefix = "ExampleWizardTransaction.ClientDetails"
}
//Hard coded this to ExampleWizardTransaction.ClientDetails and the bindings now work
};
return helper.Partial(partialViewName, result, viewData);
}
It seems I need to be able to get the name of the wizard object and the current step object as a string value to pass into TemplateInfo.
I'm gonna take a wild guess at your class structures. Assuming your classes are something like this:
[AttributeUsage(AttributeTargets.Property, AllowMultiple =false)]
public class StepAttribute: Attribute
{
public StepEnum Step { get; set; }
}
public interface IWizardStep
{
}
public interface IWizardTransaction
{
}
public enum StepEnum
{
Previous,
CurrentStep
}
public class WizardStep: IWizardStep
{
public string StepName { get; set; }
public override string ToString()
{
return StepName;
}
}
public class Wizard : IWizardTransaction
{
[Step(Step = StepEnum.Previous)]
public WizardStep ClientDetails => new WizardStep() { StepName = "ClientDetails" };
[Step(Step = StepEnum.CurrentStep)]
public WizardStep ClientQuestions => new WizardStep() { StepName = "ClientQuestions" };
}
Assuming also this implementation of PartialFor method
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
var compiled = expression.Compile();
var result = compiled.Invoke(html.ViewData.Model);
return html.Partial(partialViewName, result);
}
Then this implementation of GetStepObject will work
public static TProperty GetStepObject<TProperty>(this IWizardTransaction wizardTransaction)
where TProperty : class
{
var properties = wizardTransaction.GetType().GetProperties()
.Where(x => x.GetCustomAttributes(typeof(StepAttribute), true).Any());
PropertyInfo #object = properties.FirstOrDefault(x =>
(x.GetCustomAttributes(typeof(StepAttribute), true).SingleOrDefault()
as StepAttribute).Step == StepEnum.CurrentStep);
return #object.GetValue(wizardTransaction) as TProperty;
}
With this implementation of a partial view named _Step.cshtml like this
#model PartialView.Models.WizardStep
#Model
Your view can call it like this
#model PartialView.Models.Wizard
#using PartialView.Models;
#{
ViewBag.Title = "Partial view calling";
}
#Html.PartialFor(m=>m.GetStepObject<WizardStep>(), "_Step")
And the visual result will be a blank page with the html text ClientQuestions
Since #Html.EditorFor() is building an input field according to the model data type. I'd like to use this method and generate a textbox for a model variable of the DATETIME type, instead of a calendar. Is there any way of doing this?
You can create your own helper to display and manage date.
public static MvcHtmlString DatePickerFor<TModel, TProperty>
(this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression)
{
string datePickerName =
ExpressionHelper.GetExpressionText(expression);
string datePickerFullName = helper.ViewContext.ViewData.
TemplateInfo.GetFullHtmlFieldName
(datePickerName);
string datePickerID = TagBuilder.CreateSanitizedId
(datePickerFullName);
ModelMetadata metadata = ModelMetadata.FromLambdaExpression
(expression, helper.ViewData);
DateTime datePickerValue = (metadata.Model == null ?
DateTime.Now : DateTime.Parse(
metadata.Model.ToString()));
TagBuilder tag = new TagBuilder("input");
tag.Attributes.Add("name", datePickerFullName);
tag.Attributes.Add("id", datePickerID);
tag.Attributes.Add("type", "date");
tag.Attributes.Add("value", datePickerValue.
ToString("yyyy-MM-dd"));
IDictionary<string, object> validationAttributes = helper.
GetUnobtrusiveValidationAttributes
(datePickerFullName, metadata);
foreach (string key in validationAttributes.Keys)
{
tag.Attributes.Add(key, validationAttributes[key].ToString());
}
MvcHtmlString html=new MvcHtmlString(
tag.ToString(TagRenderMode.SelfClosing));
return html;
}
to prepare the use of the helper
public class EmployeeMetadata
{
[Required]
public DateTime BirthDate { get; set; }
}
[MetadataType(typeof(EmployeeMetadata))]
public partial class Employee
{
}
public ActionResult Index()
{
NorthwindEntities db = new NorthwindEntities();
return View(db.Employees.First());
}
and you can use your helper
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
#using(Html.BeginForm("ProcessForm","Home",FormMethod.Post))
{
#Html.DatePicker("birthdateUnbound")
#Html.DatePickerFor(m=>m.BirthDate)
#Html.ValidationMessageFor(m=>m.BirthDate)
<input type="submit" value="Submit" />
}
To submit your form
public ActionResult ProcessForm(Employee obj)
{
return View("Index",obj);
}
I need to get working validation of the custom ASP.NET MVC helper.
Helper
public static class AutocompleteHelper
{
public static MvcHtmlString AutocompleteFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string actionUrl)
{
return CreateAutocomplete(helper, expression, actionUrl, null, null);
}
public static MvcHtmlString AutocompleteFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string actionUrl, bool? isRequired, string placeholder)
{
return CreateAutocomplete(helper, expression, actionUrl, placeholder, isRequired);
}
private static MvcHtmlString CreateAutocomplete<TModel, TValue>(HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string actionUrl, string placeholder, bool? isRequired)
{
var attributes = new Dictionary<string, object>
{
{ "data-autocomplete", true },
{ "data-action", actionUrl }
};
if (!string.IsNullOrWhiteSpace(placeholder))
{
attributes.Add("placeholder", placeholder);
}
if (isRequired.HasValue && isRequired.Value)
{
attributes.Add("required", "required");
}
attributes.Add("class", "form-control formControlAutocomplete");
attributes.Add("maxlength", "45");
Func<TModel, TValue> method = expression.Compile();
var value = method((TModel)helper.ViewData.Model);
var baseProperty = ((MemberExpression)expression.Body).Member.Name;
var hidden = helper.Hidden(baseProperty, value);
attributes.Add("data-value-name", baseProperty);
var automcompleteName = baseProperty + "_autocomplete";
var textBox = helper.TextBox(automcompleteName, null, string.Empty, attributes);
var builder = new StringBuilder();
builder.AppendLine(hidden.ToHtmlString());
builder.AppendLine(textBox.ToHtmlString());
return new MvcHtmlString(builder.ToString());
}
}
HTML
#Html.AutocompleteFor(x => x.ProductUID, Url.Action("AutocompleteProducts", "Requisition"), true, "Start typing Product name...")
#Html.ValidationMessageFor(x => x.ProductUID)
I seems like validating but no message appears.
Any clue?
The name of your text field is ProductUID_autocomplete but your ValidationMessageFor which is supposed to display the error message is bound to ProductUID.
So make sure that you are binding your error message to the same property:
#Html.ValidationMessage("ProductUID_autocomplete")
It appears that whatever custom logic you might have to validate this field is injecting the error under the ProductUID_autocomplete key in the ModelState.
This being said, why not just invoke the ValidationMessage helper inside your custom helper? This way you will have less things to type in your view and the logic with those names being suffixed with _autocomplete will stay inside the helper only.
My goal is to create an object to allow chaining of commands in MVC.Net views.
Here is an example use in a view of a menu I created using this concept:
<nav class="navigation">
<%: Html
.menu()
.item("Introduction", "Introduction", "Home")
.item("About", "About", "Home")
.item("Systems", "Index", "Systems")
/*.item("Categories", "Categories", "Health")*/
.item("Test Cases", "TestCases", "Testing")
.category("Logging")
.item("UniMon Events", "UniMonEvents", "Logging")
.end()
.end() %>
</nav>
As you can see it allows for the quick construction of a multi-tiered menu with interdependencies between the various parts.
I would like to achieve this same effect for a form using lambda expressions.
The ideal syntax would look like this:
<%: Html
.form()
.hidden(m=>m.property1)
.hidden(m=>m.property2)
.end() %>
Where I am running into trouble is with the hidden method. It seems there is no way to get the compiler to infer m without passing it to the method hidden.
I can achieve this syntax:
<%: Html
.form()
.hidden(Html, m=>m.property1)
.hidden(Html, m=>m.property2)
.end() %>
Using this class and an extension method(not shown):
public class RouteForm
{
public HtmlHelper HtmlHelper { get; private set; }
public Dictionary<string, string> PostData { get; private set; }
public RouteForm(HtmlHelper htmlHelper)
{
HtmlHelper = htmlHelper;
PostData = new Dictionary<string, string>();
}
public RouteForm hidden<TModel, TValue>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression)
{
string name = ExpressionHelper.GetExpressionText(expression);
string value = GetFieldValue(htmlHelper, expression);
PostData.Add(name, value);
return this;
}
private static string GetFieldValue<TModel, TValue>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression)
{
object oValue = expression.Compile()(htmlHelper.ViewData.Model);
string value = (oValue is Enum) ? ((int)oValue).ToString() : oValue.ToString();
return value; ;
}
public MvcHtmlString end()
{
//TODO: render form with post data
return MvcHtmlString.Empty;
}
}
I thought that perhaps a class with a generic type might be what I am looking for, so I tried this:
public class RouteForm<TModel>
{
public HtmlHelper<TModel> HtmlHelper { get; private set; }
public Dictionary<string, string> PostData { get; private set; }
public RouteForm(HtmlHelper<TModel> htmlHelper)
{
HtmlHelper = htmlHelper;
PostData = new Dictionary<string, string>();
}
public RouteForm<TModel> hidden<TModel, TValue>(Expression<Func<TModel, TValue>> expression)
{
string name = ExpressionHelper.GetExpressionText(expression);
string value = GetFieldValue(expression);
PostData.Add(name, value);
return this;//ERRORS: TModel is TModel
}
private string GetFieldValue<TModel, TValue>(Expression<Func<TModel, TValue>> expression)
{
object oValue = expression.Compile()(
(TModel)HtmlHelper.ViewData.Model //ERRORS: Cannot convert type TModel to TModel
);
string value = (oValue is Enum) ? ((int)oValue).ToString() : oValue.ToString();
return value; ;
}
public MvcHtmlString end()
{
//TODO: render form with post data
return MvcHtmlString.Empty;
}
}
I put the errors in the code above using comments.
Thanks!
You're using too many generic parameters.
Methods like GetFieldValue<TModel, ...> create a second TModel paramter which is not related to the first one.
In other words, they allow you to write
new RouteForm<PersonModel>().GetFieldValue<TruckModel, ...>()
This is obviously wrong.
Instead, just get rid of that parameter from each method and let them use the class' TModel parameter instead.
I guess the compilation error "ERRORS: TModel is TModel" is caused by declaring TModel twice in the generic declaration of hidden().
I haven't compiled this, but I'd try something like this:
public static class HtmlHelperExtensions
{
public static RouteForm<TModel> form(this HtmlHelper helper, TModel model)
{
return new RouteForm<TModel>(helper);
}
}
public class RouteForm<TModel>
{
public RouteForm<TModel> hidden(Expression<Func<TModel, TValue>> expression)
{
}
public MvcHtmlString end()
{
}
}
Thanks to both of you I was able to create a class that achieves the syntax I was looking for.
(the class is simplified for this post)
class:
public class RouteForm<TModel>
{
public HtmlHelper<TModel> HtmlHelper { get; private set; }
public RouteForm(HtmlHelper<TModel> htmlHelper)
{
HtmlHelper = htmlHelper;
}
public RouteForm<TModel> hidden<TValue>(Expression<Func<TModel, TValue>> expression)
{
return this;
}
public MvcHtmlString end()
{
return MvcHtmlString.Empty;
}
}
extension method:
public static RouteForm<TModel> form<TModel>(this HtmlHelper<TModel> helper)
{
return new RouteForm<TModel>(helper);
}
markup syntax:
<%: Html
.form()
.hidden(m=>m.Property1)
.hidden(m=>m.Property2)
.end()
%>
When using an HTML Helper, what is the best method to set an attribute based on a condition. For example
<%if (Page.User.IsInRole("administrator")) {%>
<%=Html.TextBoxFor(m => m.FirstName, new {#class='contactDetails'}%>
<%} else {%>
<%=Html.TextBoxFor(m => m.FirstName, new {#class='contactDetails', disabled = true}%>
<%}%>
There must be a better way to programmatically add just one additional KeyPair to the anonymous type? Can't use
new { .... disabled = Page.User.IsInRole("administrator") ... }
as the browser takes any disabled attribute value as making the input disabled
I could suggest you to use mvccontrib.FluentHtml.
You can do something like this
<%=this.TextBox(m=>m.FirstNam ).Disabled(Page.User.IsInRole("administrator"))%>
It works for me as well...
<%: Html.DropDownList("SportID", (SelectList)ViewData["SportsSelectList"], "-- Select --", new { #disabled = "disabled", #readonly = "readonly" })%>
<%= Html.CheckBoxFor(model => model.IsActive, new { #disabled = "disabled", #readonly = "readonly" })%>
Page.User.IsInRole("administrator") ? null : new { disabled = "disabled" }
Using #SLaks suggestion to use an Extension method, and using Jeremiah Clark's example Extension method I've written an extension method so I can now do
Html.TextBoxFor(m => m.FirstName,new{class='contactDetails', ...},Page.User.IsInRole("administrator"));
Not Sure if there's a better method though
public static class InputExtensions
{
public static IDictionary<string, object> TurnObjectIntoDictionary(object data)
{
var attr = BindingFlags.Public | BindingFlags.Instance;
var dict = new Dictionary<string, object>();
if (data == null)
return dict;
foreach (var property in data.GetType().GetProperties(attr))
{
if (property.CanRead)
{
dict.Add(property.Name, property.GetValue(data, null));
}
}
return dict;
}
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes, bool disabled)
{
IDictionary<string, object> values = TurnObjectIntoDictionary(htmlAttributes);
if (disabled)
values.Add("disabled","true");
return htmlHelper.TextBoxFor(expression, values);
}
public static MvcHtmlString TextAreaFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes, bool disabled)
{
IDictionary<string, object> values = TurnObjectIntoDictionary(htmlAttributes);
if (disabled)
values.Add("disabled", "true");
return htmlHelper.TextAreaFor(expression, values);
}
public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool>> expression, object htmlAttributes, bool disabled)
{
IDictionary<string, object> values = TurnObjectIntoDictionary(htmlAttributes);
if (disabled)
values.Add("disabled", "true");
return htmlHelper.CheckBoxFor(expression, values);
}
}
You'll need to pass a Dictionary<string, object>, and add the disabled key inside an if statement.
I recommend making an overload of the extension method that takes a bool disabled parameter and adds it to a RouteValueDictionary created from the attributes parameter if it's true. (You could also remove the disabled entry from the RouteValueDictionary if it's false, and not take another parameter)
You may also define this param that way:
Page.User.IsInRole("administrator")
? (object)new { #class='contactDetails'}
: (object)new { #class='contactDetails', disabled = true}
You may want to consider writing your own HtmlHelper Extension class with a new TextBox method:
public static class HtmlHelperExtensions
{
public static MvcHtmlString TextBoxFor(this HtmlHelper htmlHelper, Expression<Func<TModel, TProperty>> expression, string cssClass, bool disabled)
{
return disabled
? Html.TextBoxFor(expression, new {#class=cssClass, disabled="disabled"})
: Html.TextBoxFor(expression, new {#class=cssClass})
}
}
now (if this new class is in the same namespace, or you've imported the new namespace to your page header, or in the pages section of the web.config) you can do this on your aspx page:
<%=Html.TextBoxFor(m => m.FirstName, "contactDetails", Page.User.IsInRole("administrator")) %>
Created an extension method on Object that will create a copy of the input object excluding any properties that are null, and return it all as dictionary that makes it easily used in MVC HtmlHelpers:
public static Dictionary<string, object> StripAnonymousNulls(this object attributes)
{
var ret = new Dictionary<string, object>();
foreach (var prop in attributes.GetType().GetProperties())
{
var val = prop.GetValue(attributes, null);
if (val != null)
ret.Add(prop.Name, val);
}
return ret;
}
Not sure about performance implications of reflecting through properties twice, and don't like the name of the extension method much, but it seems to do the job well ...
new {
#class = "contactDetails",
disabled = Page.User.IsInRole("administrator") ? "true" : null
}.StripAnonymousNulls()