pass values from other properties on my model to custom htmlhelper - asp.net-mvc

I am trying to create a custom html helper that will actually have several html elements in it including an input type of file and input type of button.
I have that working fine, but I'd also like to include a few hidden inputs and populate the values of those with values from a few of the properties of my model. Is it possible to fetch the values of a few properties from my model in a custom #Html.MyCustomHelperFor(m => m.somefield)
my method signature looks like this:
public static MvcHtmlString MyCustomHelperFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
also, a bit off topic, would I be better suited just writing this up in a partial view?
please advise.

You can access the full model from ViewData
MyCustomHelperFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
MyModelClass model = htmlHelper.ViewData as MyModelClass;
string otherProperty = model.OtherProperty;
However this is not very flexible since it will only work for one model type (unless you do conditional checks (if (htmlHelper.ViewData is MyModelClass1) {..} else if (htmlHelper.ViewData is MyModelClass2) {..} etc.
I suggest you would be better of creating a partial view or custom display and editor templates.

Related

How can I create a custom HTMLHelper extension that handles an item with an index

In my MVC4 website, I have a page where I'm looping through a list of items that are part of my model and putting them in a table.
My model is named JobModel and it has a list of TaskModel objects accessible in a property named Tasks.
I'm using syntax like this for each item in the list...
#Html.DisplayFor(m => m.Tasks[i].TaskName)
#Html.DisplayFor(m => m.Tasks[i].TaskName)
This works fine.
However, I have a need to create a Custom DisplayFor extension, that I'll name DisplayFor2 for this discussion.
My code looks like this...
public static IHtmlString DisplayFor2<TProperty>(this HtmlHelper<TaskModel> html, Expression<Func<TaskModel, TProperty>> ex) {
//Build my custom html here...
}
When I attempt this code,
#Html.DisplayFor2(m => m.Tasks[i].TaskName)
I get the compiler complaining that:
The type arguments for method 'IHtmlString MySite.HtmlHelpers.HelperExtensions.DisplayFor2<TProperty>(this HtmlHelper<TaskModel>, Expression<Func<TaskModel,TProperty>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
What do I need to do?
*UPDATE*
I changed my method to this, and now it works.
public static IHtmlString TableCellFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> ex)
But when I do this, I don't know the index of the child item, so it's proven useless.

EditorFor with the same model type twice

I'm dealing with this problem.
I'm using EditorFor for a model which is an abstract class.
#model Contoso.Core.Base.Question
#Html.HiddenFor(model => Model.Id)
#Html.Hidden("ModelType", Model.GetType().AssemblyQualifiedName)
#Html.EditorFor(model => Model, Contoso.Core.QuestionRepositoryManager.GetQuestionView(Model))
As you can above, later invoke a concrete editor for the model and what does MVC is only render the first one (I mean, print hidden input fields "Id and ModelType", but not for the inputs in the concrete EditorFor).
How can I do to print these two EditorFor? I was reading about using PartialView, but I don't like this idea because I don't know how to bind these properties in the POST Method.
UPDATE:
I'm not sure if do I have to modify the prefixHtml to fix this?
ViewData.TemplateInfo.HtmlFieldPrefix = ...
It renders the second template because if I change to Editor instead of EditorFor
#Html.Editor("abc", Contoso.Core.QuestionRepositoryManager.GetQuestionView(Model))
I believe the problem here is that the compiler is choosing the wrong overload. If you look at the overloads of EditorFor, you see there are two that take two parameters:
public static MvcHtmlString EditorFor<TModel, TValue>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression,
Object additionalViewData
)
and
public static MvcHtmlString EditorFor<TModel, TValue>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression,
string templateName
)
You could try this:
#Html.EditorFor(model => Model,
Contoso.Core.QuestionRepositoryManager.GetQuestionView(Model) as string)
or you could do this:
#Html.EditorFor(model => Model,
Contoso.Core.QuestionRepositoryManager.GetQuestionView(Model), null)
There are also two constructors that take 3 arguments, but either of them, the second argument is always the template name, so by passing null it really doesn't matter which is chosen.
The problem may also be that GetQuestionView() returns an object instead of a string, and that is why it's choosing the wrong constructor, making sure the return type of GetQuestionView() is string may also fix it. Although I'm not sure why it works with Editor, since the same problem would exist there as the constructors are pretty analogous.

How to create custom strongly typed HtmlHelper?

For example when using a
#Html.TextBoxFor(v => Model.Text);
and submitting the form, the value in the TextBox is automatically assigned to Model.Text.
I want to write such a custom HTML helper.
I know how to write such a extension method for displaying HTML, but how to program the behaviour to assign the value Model.WhatEver.
I want to build a Tag-Cloud and I think it is a good idea to include it in a way like:
#Html.TagCloud(v => Model.SelectedTags, Model.AvailableTags)
The HTML-Helper just prints out HTML, but how can I assign values to Model.SelectedTags?
See how this method is defined:
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression);
It is just generic extension method in static class returning MvcHtmlString. Just write one yourself and insert it into view via web.config.
<pages>
<namespaces>
<add namespace="NameSpaceWithYourClass" />
Remeber that this method have to take this HtmlHelper<TModel> as extension parameter and
Expression<Func<TModel, TProperty>> expression as expression for selecting property and you will be fine.
BTW: i think for your need would be better partial view since HTML for it can be quite complicated.

MVC3: set default DisplayFor template

The default DisplayFor helper just throws some escaped text on the page. Is there a way to wrap the default in a <span> or <div> without writing custom DisplayFor templates for each property you want to display? Instead of it spewing out
Foo
I want it to spit out
<span>Foo</span>
I've seen a lot of articles for making the custom stuff; I use those quite often. But most of my display stuff I just want to handle with simple CSS where the content is wrapped in a tag, and I can't find a reference on how to do this, or even if it's possible.
Sure use the 'object' template and override as follows from ViewData.ModelMetadata.Properties
Object template - Html.Display using ViewBag
By having your display template named object.cshtml you will apply to all types with numerable properties
There's a link to brad wilsons entry that contains the primary details
http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html
You could create your own HtmlHelper extension
public static class HtmlHelperExtensions
{
public static MvcHtmlString EnhancedDisplayFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
var stringBuilder = new StringBuilder("<span>");
stringBuilder.Append(html.DisplayFor(expression));
stringBuilder.Append("</span>");
return new MvcHtmlString(stringBuilder.ToString());
}
}
and then use it like this
#Html.EnhancedDisplayFor(model => model.MyProperty);

ASP.Net MVC Html.HiddenFor with wrong value

I'm using MVC 3 in my project, and I'm seeing a very strange behavior.
I'm trying to create a hidden field for a particular value on my Model, the problem is that for some reason the value set on the field does not correspond to the value in the Model.
e.g.
I have this code, just as a test:
<%:Html.Hidden("Step2", Model.Step) %>
<%:Html.HiddenFor(m => m.Step) %>
I would think that both hidden fields would have the same value.
What I do is, set the value to 1 the first time I display the View, and then after the submission I increase the value of the Model field by 1.
So, the first time I render the page both controls have the value 1, but the second time the values rendered are these:
<input id="Step2" name="Step2" type="hidden" value="2" />
<input id="Step" name="Step" type="hidden" value="1" />
As you can see, the first value is correct, but the second value seems to be the same as the first time I display the View.
What am I missing? Are the *For Html helpers caching the values in some way? If so, how can I disable this caching?.
Thanks for your help.
That's normal and it is how HTML helpers work. They first use the value of the POST request and after that the value in the model. This means that even if you modify the value of the model in your controller action if there is the same variable in the POST request your modification will be ignored and the POSTed value will be used.
One possible workaround is to remove this value from the model state in the controller action which is trying to modify the value:
// remove the Step variable from the model state
// if you want the changes in the model to be
// taken into account
ModelState.Remove("Step");
model.Step = 2;
Another possibility is to write a custom HTML helper which will always use the value of the model and ignore POST values.
And yet another possibility:
<input type="hidden" name="Step" value="<%: Model.Step %>" />
I encountered the same problem when writing a Wizard that shows different parts of a larger model at every step.
Data and/or Errors from "Step 1" would become mixed up with "Step 2", etc, until I finally realized that ModelState was to 'blame'.
This was my simple solution:
if (oldPageIndex != newPageIndex)
{
ModelState.Clear(); // <-- solution
}
return View(model[newPageIndex]);
This code will not work
// remove the Step variable from the model state
// if you want the changes in the model to be
// taken into account
ModelState.Remove("Step");
model.Step = 2;
...because HiddenFor always (!) reads from ModelState not the model itself. And if it doesn't find the "Step" key it will produce the default for that variable type which will be 0 in this case
Here is the solution. I wrote it for myself but don't mind sharing it cause I see many people are struggling with this naughty HiddenFor helper.
public static class CustomExtensions
{
public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
ReplacePropertyState(htmlHelper, expression);
return htmlHelper.HiddenFor(expression);
}
public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
{
ReplacePropertyState(htmlHelper, expression);
return htmlHelper.HiddenFor(expression, htmlAttributes);
}
public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
{
ReplacePropertyState(htmlHelper, expression);
return htmlHelper.HiddenFor(expression, htmlAttributes);
}
private static void ReplacePropertyState<TModel, TProperty>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
string text = ExpressionHelper.GetExpressionText(expression);
string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(text);
ModelStateDictionary modelState = htmlHelper.ViewContext.ViewData.ModelState;
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
if (modelState.ContainsKey(fullName))
{
ValueProviderResult currentValue = modelState[fullName].Value;
modelState[fullName].Value = new ValueProviderResult(metadata.Model, Convert.ToString(metadata.Model), currentValue.Culture);
}
else
{
modelState[fullName] = new ModelState
{
Value = new ValueProviderResult(metadata.Model, Convert.ToString(metadata.Model), CultureInfo.CurrentUICulture)
};
}
}
}
Then you just use it as usual from within you view:
#Html.HiddenFor2(m => m.Id)
It worth to mention it works with collections too.
I am too struggling with the same situation I think, where I use the same model state between calls, and when I alter a model property on backend. Though, it does not matter for me, if I use textboxfor or hiddenfor.
I just bypass the situation by using page scripts to store the model value as a js variable, because I need the hiddenfield for that purpose in the beginning.
Not sure if this helps but just consider..

Resources