How can I use an #HtmlHelper inside a custom #HtmlHelper? - asp.net-mvc

I am trying to create a custom Html helper with ASP.NET MVC. I have the following code:
#helper DefaultRenderer(Models.Control control)
{
<div class="form-group">
<label class="control-label" for="#control.Name">#control.Label</label>
#Html.TextBoxFor(m => control.Value, new { #class = "form-control" })
</div>
}
Apparently #Html.TextBoxFor cannot be found inside a Helper .cshtml class. I can use it in a partial view which is also a .cshtml class.
I can use #HtmlTextBox but then I will lose the strong model binding...
Why is this happening and is there a way to get it to work?

No, this is not possible. You could not 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, Control control , object htmlAttributes)
{
var sb = new StringBuilder();
var dtp = htmlHelper.TextBoxFor(expression, htmlAttributes).ToHtmlString();
sb.AppendFormat("<div class='form-group'><label class='control-label' for='{1}'>{2}</label>{0}</div>", dtp,control.Name,control.Label);
return MvcHtmlString.Create(sb.ToString());
}
}
Then you can use :
#html.DefaultRenderer(m => m.Control.Value, Models.Control,new { #class = "form-control" }

Related

ASP.NET MVC 3 diplay label + data in readonly scenario using templated HtmlHelpers

I need to display short description like label and near it data associated with this description.
For editing data it looks like
<div class="field">
#Html.LabelFor(m => m.CustomerTaxId)
#Html.EditorFor(m => m.CustomerTaxId)
</div>
Now I want to do the same thing for readonly data. I've written next code
<div>
#Html.LabelFor(m => m.TotalAmount)
#Html.DisplayFor(m => m.TotalAmount)
</div>
It seems working but it breaks semantic meaning of label tag which must be used for input tags only.
Is there a way in MVC 3 to get something like
<div>
<span class="label">Total amount</span>
<span class="value">1500.00 $</span>
</div>
with minimal efforts (think it can be done with heavy template usage)
It seems like I can overwrite template for Html.DisplayFor how to deal with Html.LabelFor?
You can create custom html helper as below,
public static MvcHtmlString Span<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression, object htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData, null);
string spanInnerText = metadata.DisplayName ?? metadata.PropertyName;
TagBuilder tag = new TagBuilder("span");
tag.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
tag.SetInnerText(spanInnerText);
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}
You could use in a view as,
#Html.Span(m => m.Name, new { #class = "label" })

get the generated clientid for a form field

Is there a way to get the client id generated for a field generated with a html helper?
i have several components that sometimes are inside another forms, and i wan't to attach javascript events to them.
so, for sample, i would like to have:
#Html.TextBoxFor(model => model.client.email)
<script>
$('##getmyusercontrolid(model=>model.client.email)').val("put your mail here");
</script>
I use this helper:
public static partial class HtmlExtensions
{
public static MvcHtmlString ClientIdFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
return MvcHtmlString.Create(htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression)));
}
}
Use it just as you would any other helper: #Html.ClientIdFor(model=>model.client.email)
MVC 5 has NameFor, so your example using it is:
#Html.TextBoxFor(model => model.client.email)
<script>
$('##Html.NameFor(model => model.client.email)').val("put your mail here");
</script>
EDIT
IdFor is also available, which may be more appropriate in this situation than NameFor.
Use #Html.IdFor(model => model.client.email) as this functionality already exists like so:
#Html.TextBoxFor(model => model.client.email)
<script>
$('##Html.IdFor(model => model.client.email)').val("put your mail here");
</script>
You can read more about it at the following page:
https://msdn.microsoft.com/en-us/library/hh833709%28v=vs.118%29.aspx
You could try specifying the id in the HtmlAttributes argument when you generate the input field, e.g.
#Html.TextBoxFor(model => model.client.email, new { id = "emailTextBox" })
Then you can use this in your javaScript
var email = $('#emailTextBox').val();

ASP.NET MVC html helper code used to convert model to PropertyName

This code:
#Html.HiddenFor(model => model.Country.CountryId)
gives this output:
<input type="hidden" id="Country_CountryId" name="Country.CountryId" />
Is there a way to get the id of the above code by using just the model? For eg.
#Html.TestBoxFor(model => model.Country.CountryName, new { data_HiddenId= model.Country.CountryId.GetHtmlRenderOrSomething())
which should give this:
<input type="text" id="Country_CountryName" name="Country.CountryName" data-HiddenId="Country_CountryId" />
My main concern is data-HiddenId. I want it to be equal to the id generated for the hidden input above.
You could try this:
#Html.TestBoxFor(
model => model.Country.CountryName,
new {
data_hiddenid = ExpressionHelper.GetExpressionText((Expression<Func<ModelType, int>>)(x => x.Country.CountryId))
}
)
and to reduce the ugliness you could write a custom HTML helper:
public static class HtmlExtensions
{
public static MvcHtmlString TextBoxWithIdFor<TModel, TProperty, TId>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
Expression<Func<TModel, TId>> idExpression
)
{
var id = ExpressionHelper.GetExpressionText(idExpression);
return htmlHelper.TextBoxFor(expression, new { data_hiddenid = id });
}
}
and then simply:
#Html.TestBoxWithIdFor(
model => model.Country.CountryName,
model => model.Country.CountryId
)

Is it possible to create a generic #helper method with Razor?

I am trying to write a helper in Razor that looks like the following:
#helper DoSomething<T, U>(Expression<Func<T, U>> expr) where T : class
Unfortunately, the parser thinks that <T is the beginning of an HTML element and I end up with a syntax error. Is it possible to create a helper with Razor that is a generic method? If so, what is the syntax?
This is possible to achieve inside a helper file with the #functions syntax but if you want the razor-style readability you are referring to you will also need to call a regular helper to do the HTML fit and finish.
Note that functions in a Helper file are static so you would still need to pass in the HtmlHelper instance from the page if you were intending to use its methods.
e.g.
Views\MyView.cshtml:
#MyHelper.DoSomething(Html, m=>m.Property1)
#MyHelper.DoSomething(Html, m=>m.Property2)
#MyHelper.DoSomething(Html, m=>m.Property3)
App_Code\MyHelper.cshtml:
#using System.Web.Mvc;
#using System.Web.Mvc.Html;
#using System.Linq.Expressions;
#functions
{
public static HelperResult DoSomething<TModel, TItem>(HtmlHelper<TModel> html, Expression<Func<TModel, TItem>> expr)
{
return TheThingToDo(html.LabelFor(expr), html.EditorFor(expr), html.ValidationMessageFor(expr));
}
}
#helper TheThingToDo(MvcHtmlString label, MvcHtmlString textbox, MvcHtmlString validationMessage)
{
<p>
#label
<br />
#textbox
#validationMessage
</p>
}
...
No, this is not currently possible. You could write a normal HTML helper instead.
public static MvcHtmlString DoSomething<T, U>(
this HtmlHelper htmlHelper,
Expression<Func<T, U>> expr
) where T : class
{
...
}
and then:
#(Html.DoSomething<SomeModel, string>(x => x.SomeProperty))
or if you are targeting the model as first generic argument:
public static MvcHtmlString DoSomething<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expr
) where TModel : class
{
...
}
which will allow you to invoke it like this (assuming of course that your view is strongly typed, but that's a safe assumption because all views should be strongly typed anyways :-)):
#Html.DoSomething(x => x.SomeProperty)
In all cases the TModel will be the same (the model declared for the view), and in my case, the TValue was going to be the same, so I was able to declare the Expression argument type:
#helper FormRow(Expression<Func<MyViewModel, MyClass>> expression) {
<div class="form-group">
#(Html.LabelFor(expression, new { #class = "control-label col-sm-6 text-right" }))
<div class="col-sm-6">
#Html.EnumDropDownListFor(expression, new { #class = "form-control" })
</div>
#Html.ValidationMessageFor(expression)
</div>
}
If your model fields are all string, then you can replace MyClass with string.
It might not be bad to define two or three helpers with the TValue defined, but if you have any more that would generate some ugly code, I didn't really find a good solution. I tried wrapping the #helper from a function I put inside the #functions {} block, but I never got it to work down that path.
if your main problem is to get name attribute value for binding using lambda expression seems like the #Html.TextBoxFor(x => x.MyPoperty), and if your component having very complex html tags and should be implemented on razor helper, then why don't just create an extension method of HtmlHelper<TModel> to resolve the binding name:
namespace System.Web.Mvc
{
public static class MyHelpers
{
public static string GetNameForBinding<TModel, TProperty>
(this HtmlHelper<TModel> model,
Expression<Func<TModel, TProperty>> property)
{
return ExpressionHelper.GetExpressionText(property);
}
}
}
your razor helper should be like usual:
#helper MyComponent(string name)
{
<input name="#name" type="text"/>
}
then here you can use it
#TheHelper.MyComponent(Html.GetNameForBinding(x => x.MyProperty))

PartialView Dynamic BeginForm Parameters

If I have the below PartialView
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.Photo>" %>
<% using (Html.BeginForm("MyAction", "MyController", FormMethod.Post, new { enctype = "multipart/form-data" })) { %>
<%= Html.EditorFor( c => c.Caption ) %>
<div class="editField">
<label for="file" class="label">Select photo:</label>
<input type="file" id="file" name="file" class="field" style="width:300px;"/>
</div>
<input type="submit" value="Add photo"/>
<%} %>
As you can see, the Action and the Controller are hard coded. Is there a way I can make them dynamic?
My goal is to have this partial view generic enough that I can use it in many places and have it submit to the Action and Controller it is sitting within.
I know I can use ViewData but really don't want to and likewise with passing a VormViewModel to the view and using the model properties.
Is there a nicer way than the two I listed above?
I Checked the source code of MVC and dig into the System.Web.Mvc --> Mvc --> Html --> FormExtensions so I find you can write some code like :
public static class FormHelpers
{
public static MvcForm BeginFormImage(this HtmlHelper htmlHelper, IDictionary<string, object> htmlAttributes)
{
string formAction = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
return FormHelper(htmlHelper, formAction, FormMethod.Post, htmlAttributes);
}
public static MvcForm FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes)
{
TagBuilder tagBuilder = new TagBuilder("form");
tagBuilder.MergeAttributes(htmlAttributes);
// action is implicitly generated, so htmlAttributes take precedence.
tagBuilder.MergeAttribute("action", formAction);
tagBuilder.MergeAttribute("enctype", "multipart/form-data");
// method is an explicit parameter, so it takes precedence over the htmlAttributes.
tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));
MvcForm theForm = new MvcForm(htmlHelper.ViewContext);
if (htmlHelper.ViewContext.ClientValidationEnabled)
{
htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"];
}
return theForm;
}
}
I'm not sure this is exactly what you really want to get but I'm sure you can get it if you change this lines in way satisfies your needs.
Hope this helps.

Resources