looking for cleaner code when embedding razor calls in text - asp.net-mvc-3

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

Related

trying to create a dynamic link in MVC 3

I'm working on my company's intranet site, On one of the pages I need to include links to .PDF files if they exist. No problem, I got that to work well enough. The problem I'm having is changing the link if the .PDF doesn't exist. Here's what I currently have:
, grid.Column(format: (item) => (File.Exists(item.FileName)==true ? #Art Work : Html.Raw("")))
I'm receiving the errors:
Argument 3: cannot convert from 'lambda expression' to 'System.Func'
AND
The best overloaded method match for 'System.Web.Helpers.WebGrid.Column(string, string, System.Func, string, bool)' has some invalid arguments
I've done some due diligence with Google and can't find anything. Can someone tell me where I'm going wrong?
I would definitely write a custom helper that will be responsible to generate the proper link:
public static class HtmlExtensions
{
public static IHtmlString LinkToFile(
this HtmlHelper htmlHelper,
string filename
)
{
var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
var file = htmlHelper.ViewContext.HttpContext.Server.MapPath(filename);
if (!File.Exists(file))
{
return MvcHtmlString.Empty;
}
var anchor = new TagBuilder("a");
anchor.Attributes["href"] = urlHelper.Content(filename);
anchor.SetInnerText("Art Work");
return new HtmlString(anchor.ToString());
}
}
and then inside the view simply use this helper:
grid.Column(format: #<text>#Html.LinkToFile((string)item.FileName)</text>)
Try something like this:
format: (item) =>
{
if (File.Exists(item.FileName))
{
return new HtmlString(string.Format("Art Work", #Url.Content(item.FileName)));
}
return string.Empty;
}

Conditional formatting in mvc

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.

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.

How can I create a RadioButtonList in a MVC View via HTML class ( Razor syntax )

I need to show my list in a RadioButtonList , some thing like this:
#Html.RadioButtonList("FeatureList", new SelectList(ViewBag.Features))
But as you know there is no RadioButtonList class in HTML Helper class and when I use :
#Html.RadioButton("FeatureList", new SelectList(ViewBag.Features))
it shows me a blank list!
// Controller codes :
public ActionResult Rules()
{
ViewBag.Features = (from m in Db.Features where m.ParentID == 3 select m.Name);
return View();
}
Html.RadioButton does not take (string, SelectList) arguments, so I suppose the blank list is expected ;)
You could 1)
Use a foreach over your radio button values in your model and use the Html.RadioButton(string, Object) overload to iterate your values
// Options could be a List<string> or other appropriate
// data type for your Feature.Name
#foreach(var myValue in Model.Options) {
#Html.RadioButton("nameOfList", myValue)
}
or 2)
Write your own helper method for the list--might look something like this (I've never written one like this, so your mileage may vary)
public static MvcHtmlString RadioButtonList(this HtmlHelper helper,
string NameOfList, List<string> RadioOptions) {
StringBuilder sb = new StringBuilder();
// put a similar foreach here
foreach(var myOption in RadioOptions) {
sb.Append(helper.RadioButton(NameOfList, myOption));
}
return new MvcHtmlString(sb.ToString());
}
And then call your new helper in your view like (assuming Model.Options is still List or other appropriate data type)
#Html.RadioButtonList("nameOfList", Model.Options)

Asp MVC 3: Modifiy Values Sent to View

as far as I understand a ModelBinder can generate class instances out of routedata/formdata.
What I'm looking for is a way to manipulate the data handed over to the view before it is consumed by the view.
What are the possiblities? Do I miss something obvious?
Thanks in advance!
EDIT
I don't want to send clear IDs to the client but encrypt them (at least in edit cases). As it happens very often I want this step as much as possible automated.
I look for something like a ModelBinder or a Attribute to attach to a method/viewmodel/...
Example:
GET
public ActionResult Edit(int id)
{
var vm = new EditArticleViewModel();
ToViewModel(repository.Get<Article>(id), vm);
return View(vm); // id is something like 5 and should be encryped before being used by the view
}
View
#model EditArticleViewModel
<div>
#Html.HiddenFor(x => x.Id) <!-- x.Id should be encrypted, not just "5" -->
...
</div>
Lg
warappa
You could do something with an action filter:
public class EncryptIDAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var vm = filterContext.Controller.ViewData.Model as EditArticleViewModel;
if(vm != null)
{
vm.ID = SomeMethodToEncrypt(vm.ID);
}
}
}
and apply it to any relevent actions:
[EncryptID]
public ActionResult Edit(int id)
{
var vm = new EditArticleViewModel();
ToViewModel(repository.Get<Article>(id), vm);
return View(vm);
}
When the page is then posted you can use a model binder to decrypt the id.
If you then wanted to apply this across multiple view models you could look at creating a custom data annotation which flags a property to be encrypted. In your action filter you can then look for any properties with this data annotation and encrypt them accordingly.
You could write a custom HiddenFor helper method that will automatically encrypt the value:
public static class HiddenExtensions
{
public static MvcHtmlString HiddenForEncrypted<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> ex)
{
var metadata = ModelMetadata.FromLambdaExpression(ex, htmlHelper.ViewData);
var name = ExpressionHelper.GetExpressionText(ex);
var value = metadata.Model;
var encryptedValue = SomeFunctionToEncrypt(value);
return htmlHelper.Hidden(name, encryptedValue);
}
}
As an alternative you could use the Html.Serialize helper in the MVCFutures assembly that does this under the covers.
So basically you will write in your view:
#Html.Serialize("id", Model.Id, SerializationMode.Encrypted)
and in your controller:
public ActionResult Edit([Deserialize(SerializationMode.Encrypted)]int id)
{
...
}

Resources