Conditional formatting in mvc - asp.net-mvc-3

Css:
.placeHolder {
color: darkgray
}
View:
Company: #Html.PlaceholderTag(#Model.CompanyName, "Add a
company")
Extension Method:
public static class HtmlHelpers
{
public static string PlaceholderTag(this HtmlHelper helper, string src, string placeHolder)
{
return string.IsNullOrEmpty(src) ? placeHolder : src;
}
}
The idea is is the companyname is populated it shall be shown in black otherwise it should show the string "Add a company" in dark grey color.
How could I achieve this? The example above outputs always in grey.
UPDATE
There are two solutions to this. Creating a ViewModel to wrap the model and add additional flags and View uses a C# conditional on the flags to decide what to do:
Or alternatively using an extension method for an additional HTMLHelper. Effectively MVC does that out of the box with HTml.EditorFor() etc.
public static MvcHtmlString PlaceholderTag(this HtmlHelper helper, string src, string placeHolder)
{
const string original = "<span>{0}</span>";
const string formattedPlaceholder = "<span class=\"placeHolder\">{0}</span>";
return string.IsNullOrEmpty(src) ? new MvcHtmlString(string.Format(formattedPlaceholder, placeHolder)) : new MvcHtmlString(string.Format(original, src));
}

You could try the following :
public static class HtmlHelpers
{
public static MvcHtmlString PlaceholderTag(this HtmlHelper helper, string src, string placeHolder)
{
const string formattedPlaceholder = "<span class=\"placeholder\">{0}</span>";
return string.IsNullOrEmpty(src) ? new MvcHtmlString(string.Format(formattedPlaceholder, src)) : src;
}
}

Add if logic to the view. Set the class or color or whatever you want one way if the companyname is populated and another way if it isn't.

Related

How can you define sections with default content in MVC 6?

I am currently trying to migrate an ASP.net MVC 5 project to MVC 6.
How would I migrate the following code:
public static class SectionExtensions
{
public static HelperResult RenderSection(this WebPageBase webPage, [RazorSection] string name, Func<dynamic, HelperResult> defaultContents)
{
return webPage.IsSectionDefined(name) ? webPage.RenderSection(name) : defaultContents(null);
}
}
[RazorSection] is part of the JetBrains.Annotations assembly.
Instead of WebPageBase I used RazorPage in Microsoft.AspNet.Mvc.Razor
namespace Stackoverflow
{
public static class SectionExtensions
{
public static IHtmlContent RenderSection(this RazorPage page, [RazorSection] string name, Func<dynamic, IHtmlContent> defaultContents)
{
return page.IsSectionDefined(name) ? page.RenderSection(name) : defaultContents(null);
}
}
}
Then reference the class in the razor page #using static Stackoverflow.SessionExtensions, and call it like so:
#this.RenderSection("extra", #<span>This is the default!</span>))
An alternative way would be to just do this in the view (I prefer this way, seems a lot simpler):
#if (IsSectionDefined("extra"))
{
#RenderSection("extra", required: false)
}
else
{
<span>This is the default!</span>
}
I hope this helps.
Update 1 (from comments)
by referencing the namespace
#using Stackoverflow
you don't have to include the static keyword, but when calling it, you will have to reference the actual class in the namespace and also pass 'this' into the function.
#SectionExtensions.RenderSection(this, "extra", #<span>This is the default!</span>)
Update 2
There is a bug in razor that does not allow you to call template delegates Func <dynamic, object> e = #<span>#item</span>; within a section. Please see https://github.com/aspnet/Razor/issues/672
A current workaround:
public static class SectionExtensions
{
public static IHtmlContent RenderSection(this RazorPage page, [RazorSection] string name, IHtmlContent defaultContents)
{
return page.IsSectionDefined(name) ? page.RenderSection(name) : defaultContents;
}
}
and then the razor page:
section test {
#this.RenderSection("extra", new HtmlString("<span>this is the default!</span>"));
}

How can I set the width of a textbox in asp.net mvc 3?

How can I set the width of a textbox in asp.net mvc 3? this does not work:
#Html.TextBox("Name", new { style = "width:500px" })
try this, this should work..
#Html.TextBox("Name", new { #class= "mySize" })
.mySize
{
width: 500px;
}
also in your code,try adding a semicolon and see if that works, something like this
#Html.TextBox("Name", new { style = "width:500px;" })
I'm surprised the answer given by #Yasser works. Since these extension methods are overloaded with functions that can take several anonymous objects, it's easy inadvertently use the wrong one.
From MSDN, it looks like you're calling this method:
public static MvcHtmlString TextBox(
this HtmlHelper htmlHelper,
string name,
Object value)
where value is:
The value of the text input element. If this value is null, the value of the element is retrieved from the ViewDataDictionary object. If no value exists there, the value is retrieved from the ModelStateDictionary object.
So value is used to populate the input. Instead, I think you want this extension:
public static MvcHtmlString TextBox(
this HtmlHelper htmlHelper,
string name,
Object value,
Object htmlAttributes)
Then, use it like this (pass null for value) to add inline styles to the markup:
#Html.TextBox("Name", null, new { style = "width:500px;" })
Or:
#Html.TextBox("Name", null, new { #class = "myStyle" })

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.

ASP.NET MVC - Extending TextBoxFor without re-writing the method

Is there any possible way to extend the basic html helpers (TextBoxFor, TextAreaFor, etc) using extension methods on their output, instead of just re-writing the entire methods completely? For instance, adding in ...
#Html.TextBoxFor( model => model.Name ).Identity("idName")
I know I can achieve this using the following, already..
#Html.TextBoxFor( model => model.Name, new { #id = "idName" })
But that gets clunky and frustrating to manage when you have to start adding a lot of properties. Is there any way to add extensions to these inherently without just passing in htmlAttributes for every little detail?
As #AaronShockley says, because TextBoxFor() returns an MvcHtmlString, your only option for developing a 'fluid API' style of amending the output would be to operate on the MvcHtmlStrings returned by the helper methods. A slightly different way of doing this which I think approaches what you're after would be to use a 'property builder' object, like this:
public class MvcInputBuilder
{
public int Id { get; set; }
public string Class { get; set; }
}
...and to set up extension methods like this:
public static MvcHtmlString TextBoxFor<TModel, TProp>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProp>> expression,
params Action<MvcInputBuilder>[] propertySetters)
{
MvcInputBuilder builder = new MvcInputBuilder();
foreach (var propertySetter in propertySetters)
{
propertySetter.Invoke(builder);
}
var properties = new RouteValueDictionary(builder)
.Select(kvp => kvp)
.Where(kvp => kvp.Value != null)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
return htmlHelper.TextBoxFor(expression, properties);
}
You can then do stuff like this in your View:
#this.Html.TextBoxFor(
model => model.Name,
p => p.Id = 7,
p => p.Class = "my-class")
This gives you strong typing and intellisense for input properties, which you could customise for each extension method by adding properties to an appropriate MvcInputBuilder subclass.
All of the basic html helpers return an object of type System.Web.Mvc.MvcHtmlString. You can set up extension methods for that class. Here is an example:
public static class MvcHtmlStringExtensions
{
public static MvcHtmlString If(this MvcHtmlString value, bool check)
{
if (check)
{
return value;
}
return null;
}
public static MvcHtmlString Else(this MvcHtmlString value, MvcHtmlString alternate)
{
if (value == null)
{
return alternate;
}
return value;
}
}
Then you can use these in a view like:
#Html.TextBoxFor(model => model.Name)
.If(Model.Name.StartsWith("A"))
.Else(Html.TextBoxFor(model => model.LastName)
To make extension methods that modify attributes on the rendered HTML tag, you'll have to convert the result to a string, and find and replace the value you're looking for.
using System.Text.RegularExpressions;
public static MvcHtmlString Identity(this MvcHtmlString value, string id)
{
string input = value.ToString();
string pattern = #"(?<=\bid=")[^"]*";
string newValue = Regex.Replace(input, pattern, id);
return new MvcHtmlString(newValue);
}
public static MvcHtmlString Name(this MvcHtmlString value, string id)
{
string input = value.ToString();
string pattern = #"(?<=\bname=")[^"]*";
string newValue = Regex.Replace(input, pattern, id);
return new MvcHtmlString(newValue);
}
The id and name attributes are always added by the html helpers, but if you want to work with attributes that may not be there (and you'll have to add them instead of just replacing them), you'll need to modify the code.

looking for cleaner code when embedding razor calls in text

I used to have something like this:
We suggest you read our #Html.ActionLink("help page", "Help", "Home") before
proceeding.
nice and clean. then I decided we needed to internationalise the app. I couldn't figure out a better way to deal with the above than to store the following string in the resource file:
We suggest you read our [HelpPage] before proceeding.
and then on the view I have to do:
#MvcHtmlString.Create(this.Resource("Help").ToString()
.Replace("[HelpPage]",
#Html.ActionLink("help page", "Help", "Home").ToString()
)
)
What other strategies can you use to internationalize using Razor?
this.Resource() is a page extension that calls .GetLocalResourceObject() and returns an MvcHtmlString
You should create a separate code-behind method that replaces any [placeholder]s with actual links, then call that helper in Razor views.
This will give you a single place to change the code that fills in the links.
I was having the same problem. Instead of using placeholders, I use the same formatting in my resource strings as if I were using String.Format().
An example of using this; my resource strings
LogOnText1 | Please enter your user name and password. {0} if you don't have an account.
LogOnText1Register | Register
and my view (razor):
#MvcHtmlString.Create(String.Format(ViewRes.AccountStrings.LogOnText1,
Html.ActionLink(ViewRes.AccountStrings.LogOnText1Register, "Register")))
I think it's a bit cleaner
so here's what I ended up writing:
public static class PageExtensions
{
public static MvcHtmlString Resource(
this WebViewPage page, string key,
Dictionary<string, MvcHtmlString> tokenMap
) {
HttpContextBase http = page.ViewContext.HttpContext;
string text = (string) http.GetLocalResourceObject(page.VirtualPath, key);
return new TagReplacer(text, tokenMap).ToMvcHtmlString();
}
where the tag replacements gets done like this:
public class TagReplacer
{
Dictionary<string, MvcHtmlString> tokenmap;
public string Value { get; set; }
public TagReplacer(string text, Dictionary<string, MvcHtmlString> tokenMap)
{
tokenmap = tokenMap;
Regex re = new Regex(#"\[.*?\]", RegexOptions.IgnoreCase);
Value = re.Replace(text, new MatchEvaluator(this.Replacer));
}
public string Replacer(Match m)
{
return tokenmap[m.Value.RemoveSet("[]")].ToString();
}
public MvcHtmlString ToMvcHtmlString()
{
return MvcHtmlString.Create(Value);
}
}
so in my code I can now call it like this:
#{
Dictionary<string, MvcHtmlString> tagmap = new Dictionary<string, MvcHtmlString>() {
{ "HelpPage", Html.ActionLink("help page", "Help", "Home") }
};
}
and elsewhere:
#this.Resource("Help", tagmap)
any suggestions for improvement most welcome

Resources