trying to create a dynamic link in MVC 3 - asp.net-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;
}

Related

MVC 3 How to tell what view a controller action is being called from-

Is there a way to tell what view a controller action is being called from?
For example, I would like to use "ControllerContext.HttpContext.Request.PhysicalPath" but it returns the path in which the controller action itself is located:
public ActionResult HandleCreateCustomer()
{
// Set up the customer
//..code here to setup the customer
//Check to see of the calling view is the BillingShipping view
if(ControllerContext.HttpContext.Request.PhysicalPath.Equals("~/Order/BillingShipping"))
{
//
return RedirectToAction("OrderReview", "Order", new { id = customerId });
}
else
{
return RedirectToAction("Index", "Home", new { id = customerId });
}
}
If you have a fixed number of locations that it could possibly be called from, you could create an enum where each of the values would correspond to a place where it could have been called from. You'd then just need to pass this enum value into HandleCreateCustomer, and do your condition statement(s) based on that.
At the moment I am using something of the sort:
In the View I am populating a TempData variable using:
#{TempData["ViewPath"] = #Html.ViewVirtualPath()}
The HtmlHelper method ViewVirtualPath() is found in the System.Web.Mvc.Html namespace (as usual) and is as follows and returns a string representing the View's virtual path:
public static string ViewVirtualPath(this HtmlHelper htmlHelper)
{
try{
return ((System.Web.WebPages.WebPageBase)(htmlHelper.ViewDataContainer)).VirtualPath;
}catch(Exception){
return "";
}
}
I will then obviously read the TempData variable in the controller.
I found another way.
In the controller you want to know what page it was called from.
I added the following in my controller
ViewBag.ReturnUrl = Request.UrlReferrer.AbsolutePath;
Then in the View I have a 'Back' button
#(Html.Kendo().Button().Name("ReturnButton")
.Content("Back to List").Events(e => e.Click("onReturn"))
.HtmlAttributes(new { type = "k-button" })
)
Then the javascript for the onReturn handler
function onReturn(e) {
var url = '#(ViewBag.ReturnUrl)';
window.location.href = url;
}

MVC 3 field validation keep focus on failure

We are working on some accessibility standards for a basic website and need to have focus returned to a username field if it is has failed validation as defined in the model. I have read several posts that indicate this is the behavior as it exists. But, this is not the behavior we are seeing. I am open to mvc natice functionality (we are using mvc 3 with razor) or jquery)
I have read several posts that indicate this is the behavior as it exists
Weird, where did you those posts? I would recommend you notifying the author of such posts that this is not a default behavior.
And of course when something is not the default behavior, if you want to achieve it, you will have to implement it. For example you haven't exactly specified how it should behave if there are multiple errors: which field should be focused? The first? The third? The seventh?
Let's suppose that you want to focus the first. Assuming you are using jQuery you could add the following to your view:
#if (!ViewData.ModelState.IsValid)
{
var key = ViewData
.ModelState
.Where(x => x.Value.Errors.Count > 0)
.Select(x => x.Key)
.FirstOrDefault();
if (!string.IsNullOrEmpty(key))
{
<script type="text/javascript">
$(function () {
$(':input[name=#Html.Raw(Json.Encode(key))]').focus();
});
</script>
}
}
and you are pretty much done. Well, almost, a further improvement of this code would of course be to externalize this into a reusable HTML helper to avoid transforming your views into something absolutely horrible. For example you could have a custom helper which would implement this behavior and all you have to do is add the following to your _Layout:
#Html.FocusOnFirstError()
Could be implemented with something along the lines of:
public static class HtmlExtensions
{
public static IHtmlString FocusOnFirstError(this HtmlHelper htmlHelper)
{
if (htmlHelper.ViewData.ModelState.IsValid)
{
return MvcHtmlString.Empty;
}
var key = htmlHelper
.ViewData
.ModelState
.Where(x => x.Value.Errors.Count > 0)
.Select(x => x.Key)
.FirstOrDefault();
if (string.IsNullOrEmpty(key))
{
return MvcHtmlString.Empty;
}
var script = new TagBuilder("script");
script.Attributes["type"] = "text/javascript";
script.InnerHtml = string.Format(
"$(function() {{ $(':input[name={0}]').focus(); }});",
Json.Encode(key)
);
return new HtmlString(script.ToString(TagRenderMode.Normal));
}
}

ExpressionHelper.GetExpressionText(expression) not returning the name of my property

I have implemented a MVC Extension to format the numbers in my application. It is based off the code found here. And is as follows
public static MvcHtmlString DecimalBoxFor<TModel>(this HtmlHelper<TModel> html, Expression<Func<TModel, double?>> expression, string format, object htmlAttributes = null)
{
var name = ExpressionHelper.GetExpressionText(expression);
double? dec = expression.Compile().Invoke(html.ViewData.Model);
var value = dec.HasValue ? (!string.IsNullOrEmpty(format) ? dec.Value.ToString(format) : dec.Value.ToString()): "";
return html.TextBox(name, value, htmlAttributes);
}
When I call it with the following line of Razor syntax
#Html.DecimalBoxFor(model => Model.PointAttributes[i].Data.Y,"0.000", new { #class = "span1 number" })
I get an exception because the variable 'name' in my extension is an empty string. I have tried changing the var name line to this but it only gives me the property name of 'Y' and not the full 'Model.PointAttributes[i].Data.Y' that I need to bind the model back for MVC.
var name = ((expression.Body is MemberExpression ?((MemberExpression)expression.Body).Member : ((MemberExpression)((UnaryExpression)expression.Body).Operand).Member)).Name;
Try using this function:
static public string GetExpressionText(LambdaExpression p)
{
if (p.Body.NodeType == ExpressionType.Convert || p.Body.NodeType == ExpressionType.ConvertChecked)
{
p = Expression.Lambda(((UnaryExpression)p.Body).Operand,
p.Parameters);
}
return ExpressionHelper.GetExpressionText(p);
}
This is a known behavior. I have figured out writing my own version of ExpressionHelper that handle that specific case. Now you have two option:
Use the NuGet package:
Install-Package Mariuzzo.Web.Mvc.Extras
Or just grab the source code of the aforementioned ExpressionHelper and glue it into your project.
Here a 'hybrid' one :)
public static void AddModelError<TModel>(this ModelStateDictionary state, Expression<Func<TModel, object>> expression, string message)
{
LambdaExpression lambdaExpression = null;
string fieldName = string.Empty;
if (expression.Body.NodeType == ExpressionType.Convert || expression.Body.NodeType == ExpressionType.ConvertChecked)
{
lambdaExpression = Expression.Lambda(((UnaryExpression)expression.Body).Operand, expression.Parameters);
fieldName = ExpressionHelper.GetExpressionText(lambdaExpression);
} else {
fieldName = ExpressionHelper.GetExpressionText(expression);
}
state.AddModelError(fieldName, message);
}
This one is more compact and probably a better solution:
https://stackoverflow.com/a/12689563/951001
If you can get away without using a nullable type it seems to work (i.e. remove the ? after double, or as in my case decimal). So
Expression<Func<TModel, double?>>
becomes
Expression<Func<TModel, double>>.
If you step through it with the nullable type in place you'll see the expression has a convert() function in it which seems to be the 'problem'. I'm sure like me you would be interested in how to make this function work for nullable types as well.
I know it's closed but for the record;
That's better handled by a template so you can specify what datatype you are using in the model and how it is represented in the template (single responsability).
Also you won't need to modify the MVC framework.
MSDN UiHint attribute

How do I call the placeholder watermark in a class?

I do this in an EditorTemplate View:
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue,
new { placeholder = ViewData.ModelMetadata.Watermark })
I now want to do something in a class file in which I am using TagBuilder and MergeAttribute. I use code, for example, to display an image for "required", and tried to modify it to do the same thing with placeholder. But I can't do ViewData. So this isn't working:
public static MvcHtmlString PlaceholderFor<T, TValue>
(this HtmlHelper<T> html, Expression<Func<T, TValue>> expression)
{
var placeholder = new ModelMetadata([CAN'T FIGURE OUT IN HERE]);
placeholder.MergeAttribute([ALSO CAN'T FIGURE OUT IN HERE]);
return MvcHtmlString.Create(placeholder.ToString());
}
I don't even know if the above would be appropriate so I could do something like this:
input.MergeAttribute += html.PlaceholderFor(expression);
or whether that is also the incorrect approach.
For context I am building an "input" using:
input.InnerHtml += html.EditorFor(expression);
Can any one provide a code example that would work in this context? Sorry if I am not explaining it correctly.
Your question is not very clear about what you are trying exactly to achieve. But if you want to get the contents generated by the TextBox helper inside another custom helper you could simply call this TextBox helper:
public static IHtmlString MyHelperFor<T, TValue>(
this HtmlHelper<T> html,
Expression<Func<T, TValue>> expression,
object htmlAttributes
)
{
var div = new TagBuilder("div");
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var attributes = new RouteValueDictionary(htmlAttributes);
attributes["placeholder"] = metadata.Watermark;
div.InnerHtml = html.TextBoxFor(expression, attributes).ToHtmlString();
return new HtmlString(div.ToString());
}
and then:
#Html.MyHelperFor(x => x.Test, new { #class = "foo" })

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