MVC3 HTML extension method using two bindings, instead of the usual one - asp.net-mvc-3

I'm trying to refine my HTML extension method, so that I can bind both the ID and Value components to work something like this:
#Html.AutoCompleteFor(model => model.Customer.ID, model => model.Customer.Name, "/Customer/Autocomplete")
I currently do it like this:
#Html.AutoCompleteFor(model => model.CustomerID, model => model.CustomerID_display, "/Customer/Autocomplete")
Where I need to extend the model to include CustomerID_display, which is a bit clunky, and requires very specific post processing.
The reason I need to bind on the display value (which is the text name of the entity), is so that if the user enters a new item, it can be detected and potentially auto-generated.
My expected method above doesn't work of course, and I might be way off the mark. But you should know effectively what I'm trying to do. If my expected method above worked, my implementation of AutoCompleteFor would be quite simple (everyone is using the word trivial these days!). I'm expecting that lambda can be a bit more useful, perhaps:
#Html.AutoCompleteFor(model => new AutoCompleteBinding { ID = model.Customer.ID, Name = model => model.Customer.Name }, "/Customer/Autocomplete")
Thanks!

In your HTML "...For" extensions, you normally only have a single Expression parameter.
public static MvcHtmlString AutoCompleteFor<TModel, TValue>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TValue>> expression,
object postingTextBoxHtmlAttributes,
string extraArguments,
string sourceURL
)
{
//...
}
Simply expand this, to include another expression. Adding:
Expression<Func<TModel, TValue>> expression
To make:
public static MvcHtmlString AutoCompleteFor<TModel, TValue>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TValue>> expression,
Expression<Func<TModel, TValue>> expression2,
object postingTextBoxHtmlAttributes,
string extraArguments,
string sourceURL
)
{
//...
}
You can now use expression2, as you would normally with expression. (Of course you should name it something intuitive).
Usage will be as described in the question:
#Html.AutoCompleteFor(model => model.Customer.ID, model => model.Customer.Name, "/Customer/Autocomplete")

Related

How to return #Html.ActionLink

I am writing a Html Helper for my MVC 3 project.
I want to return the MvcHtmlString like "#Html.ActionLink(xxxxx)", what should I write?
Currently I have this code
public static MvcHtmlString SetFeaturedFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper,Expression<Func<TModel, TValue>> expression)
{
var isFeatured =Convert.ToBoolean(ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model.ToString());
string result = "Html.ActionLink(Delete, DeleteComment, Admin, new { Id = #thisComment.CommentId }, null)";
return MvcHtmlString.Create(result);
}
It return the whole string.... but I want the rendered string. So what should I do? Thanks everyone.
UPDATE
Looks like I can return this directly
See below code
public static MvcHtmlString SetFeaturedFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper,Expression<Func<TModel, TValue>> expression)
{
var isFeatured =Convert.ToBoolean(ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model.ToString());
string indicatorText = (isFeatured) ? "Unset Featured" : "Set Featured";
return htmlHelper.ActionLink(indicatorText, "SetFeaturedIncident", "Admin", null, null);
}
Need to import System.Web.Routing namespace.
Remove the quotes (you want to call the function, not just store the code in a string) and the # (that's Razor, not C# anyways). You might need to change Html to whatever you called the helper parameter in your (presumably) extension method.
Also, Html.ActionLink already returns MvcHtmlString so you can just put it directly after return.

MVC3: Calling a strongly typed helper from custom helper by a dynamic field access

I have strongly typed custom helper (it does nothing - just sample):
public static MvcHtmlString MyDisplayFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression
{
return DisplayExtensions.DisplayFor(htmlHelper, model => metadata.Model);
}
It works great, but I want a make storngly typed helper with strongly typed helpers inside for each property of Model
public static MvcHtmlString MyOtherFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, IList<TProperty>>> expression)
{
ModelMetadata dataModelMetadata = ModelMetadata.FromLambdaExpression<TModel, IList<TProperty>>(expression, htmlHelper.ViewData);
IList<TProperty> modelData = (IList<TProperty>)dataModelMetadata.Model;
IEnumerable<ModelMetadata> headModelMetadatas = (new DataAnnotationsModelMetadataProvider()).GetMetadataForProperties(dataModelMetadata.Model, typeof(TProperty))
foreach (var singleData in modelData)
{
StringBuilder sb = new StringBuilder();
foreach(var headModelMetadata in headModelMetadatas)
{
sb.Append(
DisplayExtensions.DisplayFor(htmlHelper, m => singleData ) //this gives me all fields displayed by DisplayFor and I want to access single one per call
.ToHtmlString());
}
}
}
As I mentioned in comment I want to append single fields (this append is simple, there's some more code hidden there which I just cut to make it cleaner) so I tried to get one field at time:
DisplayExtensions.DisplayFor(htmlHelper, m => singleData.GetType().GetProperty(headModelMetadata.PropertyName))
but this gives me just an error.
Any other good idea, for solving it? ;-)
Edit:
I made some tries and:
DisplayExtensions.DisplayFor(htmlHelper, m => singleData.GetType().GetProperty(headModelMetadata.PropertyName).GetValue(singleData, null)) still gives an error
but:
object oValue = singleData.GetType().GetProperty(headModelMetadata.PropertyName).GetValue(singleData, null);
DisplayExtensions.DisplayFor(htmlHelper, m => oValue)
works! almost as I expected.. It returns display for each property of property. Look at sample of DateTime ;-). More help needed.
Sample, for DateTime:
Day
1
DayOfWeek
Thursday
DayOfYear
335
Hour
0
Kind
Unspecified
Millisecond
0
Minute
0
Month
12
Second
0
Ticks
634582944000000000
TimeOfDay
00:00:00
Year
2011
Ok i just simply returned
MvcHtmlString.Create(oValue.ToString()).toHtmlString();
for propertys without using DisplayFor. But more phun started with nested classes and lists.. ;-)
Anyway I solved my problem.

Why does it seem that the List Scaffold Template does not respect the DisplayName attribute?

Should I be using some other attrbute that the List template respects, or am I stuck having to manually change the source to DisplayFor(m => m.someProperty) ?
I read in another thread a suggestion that ExpressionHelper.GetExpressionText should be used, but that's not exactly what I'm looking for. I want the IDE's Add View wizard to work for Lists as it does for the other scaffold templates, and I'm hoping there's an attribute i could use on my properties to get that done.
It's because the list could be empty and with an empty list you have no access to the an instance of the type you're getting the display name for. As a result you lose typed access to the particular property you're referencing.
For instance how would you know which property to use the display name for if you don't have access to the object
<th>#Html.DisplayFor(f => f....,
/*
how do you get the property you want to
access here if the list is empty
*/
)</th>
You could write an extension method that would accept the model type of course but you'll lose that typed access.
public static IHtmlString DisplayType(this HtmlHelper html, Type modelType, string propertyName) {
object modelInstance = Activator.CreateInstance(modelType);
var data = ModelMetadata.FromStringExpression(propertyName,
new ViewDataDictionary(modelInstance));
return new HtmlString(data.DisplayName ?? propertyName);
}
#Html.DisplayType(typeof(CoolModel), "Name")
Though I don't really recommend it.
small update - you could also access it via the model as well but still without typed access:
public static IHtmlString DisplayType<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression,
string propertyName) where TModel : IEnumerable {
object modelInstance =
Activator.CreateInstance(expression.Body.Type.GetGenericArguments()[0]);
var data = ModelMetadata.FromStringExpression(propertyName,
new ViewDataDictionary(modelInstance));
return new HtmlString(data.DisplayName ?? propertyName);
}

What is the location of the default Editor and Display Templates in Asp.net MVC3?

Where are the default Razor Editor and Display templates (eg String.cshtml, DateTime.cshtml) located when one installs Asp.Net MVC 3?
If you have DotPeek or Reflector you can look up the type DefaultDisplayTemplates in there you will find the templates. But be warned they are in Code format, not WebForm or razor format, so a bit more difficult to interpret.
StringTemplate
internal static string StringTemplate(HtmlHelper html)
{
return html.Encode(html.ViewContext.ViewData.TemplateInfo.FormattedModelValue);
}
(There was no default DateTime template that I could find)
DecimalTemplate
internal static string DecimalTemplate(HtmlHelper html)
{
if (html.ViewContext.ViewData.TemplateInfo.FormattedModelValue == html.ViewContext.ViewData.ModelMetadata.Model)
html.ViewContext.ViewData.TemplateInfo.FormattedModelValue = (object) string.Format((IFormatProvider) CultureInfo.CurrentCulture, "{0:0.00}", new object[1]
{
html.ViewContext.ViewData.ModelMetadata.Model
});
return DefaultDisplayTemplates.StringTemplate(html);
}
There are no default templates.Razor Editor and Display methods are extension methods of the class HtmlHelper. You can use them or you can develope your own extension methods, like this example.
public static MvcHtmlString MyTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
MvcHtmlString result = InputExtensions.TextBoxFor(helper, expression);
// do modification to result
return result;
}

How can I add a hash fragment to T4MVC route dictionary ActionResult?

I have an extension method that returns an ActionResult (simplified for demonstration purposes):
public static ActionResult GetTestActionResult(this HtmlHelper htmlHelper, int productId)
{
return MVC.Products.Details(productId);
}
I'm using this in an Html.ActionLink:
#Html.ActionLink("Product Details", Html.GetTestActionResult(Model.ProductId), new { #class = "button blue" });
I'm using a custom jQuery plugin for tabs, that uses these hash fragments for navigation. I want to add the tab which I want to open, by tagging the hash fragment onto the end of the URL.
Html.ActionLink does have an overload for the Fragment, namely:
public static MvcHtmlString ActionLink(
this HtmlHelper htmlHelper,
string linkText,
string actionName,
string controllerName,
string protocol,
string hostName,
string fragment,
Object routeValues,
Object htmlAttributes
)
However, that is full of nasty magic strings, which T4MVC is designed to remove. Is there anyway to add the fragment to the route dictionary in my static extension method (GetTestActionResult)?
Something like:
return MVC.Products.Details(productId).AddRouteValue(String.Empty, "#tab-similar-products");
I am aware that there are two similar questions and answers on SO, but they don't quite provide me with what I am looking for. I need to wrap the fragment into the ActionResult BEFORE passing it back to the view:
Including hash values in ASP.NET MVC URL routes
Create a T4MVC ActionLink with url fragment
UPDATE:
Using David Ebbo's fix below, I made the following changes. A bit hacky, but it works:
First I altered my internal function that returns an ActionResult so that it would also add the fragment as a route value (not ideal but works):
return MVC.Products.Details(productId).AddRouteValue("tab", "#tab-similar-products");
Then in the view it copies that fragment value out of the route dictionary, then removes that route value for completeness.
// get my ActionResult with the tab fragment tagged on as a route value
var actionResult = Html.GetTestActionResult(item.Key, Model.ClaimId);
// get the tab fragment value
var tabRoute = actionResult.GetRouteValueDictionary().FirstOrDefault(r => r.Key == "tab").Value ?? "none";
// remove the route value, otherwise it will get tagged to the querystring
actionResult.GetRouteValueDictionary().Remove("tab");
// display
#Html.ActionLink("Product Details", Html.GetTestActionResult(Model.ProductId), new { #class = "button blue" }, fragment: tabRoute.ToString());
I'm sure there is a prettier way to return the fragment with the ActionResult, but for the moment this works. Thanks David.
T4MVC needs new overloads to handle this. In T4MVC.tt, try changing:
public static <#=HtmlStringType #> ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, object htmlAttributes) {
return ActionLink(htmlHelper, linkText, result, new RouteValueDictionary(htmlAttributes));
}
public static <#=HtmlStringType #> ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, IDictionary<string, object> htmlAttributes) {
return htmlHelper.RouteLink(linkText, result.GetRouteValueDictionary(), htmlAttributes);
}
to
public static <#=HtmlStringType #> ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, object htmlAttributes, string protocol = null, string hostName = null, string fragment = null) {
return ActionLink(htmlHelper, linkText, result, new RouteValueDictionary(htmlAttributes), protocol, hostName, fragment);
}
public static <#=HtmlStringType #> ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, IDictionary<string, object> htmlAttributes, string protocol = null, string hostName = null, string fragment = null) {
return htmlHelper.RouteLink(linkText, null, protocol, hostName, fragment, result.GetRouteValueDictionary(), htmlAttributes);
}
you'll then be able to write something like:
#Html.ActionLink("Product Details", Html.GetTestActionResult(Model.ProductId), new { #class = "button blue" }, fragment: "#tab-similar-products")
Let me know if that works, and I'll try to get it added to the main template.

Resources