Helpers and action links in ASP.net MVC 3 - asp.net-mvc-3

So I am having a weird issue with a helper I created:
#if (Model != null)
{
<ul>
#foreach (var item in Model)
{
<li>
#Html.ActionLink(item.name, "Map", "Home", new { id = item.id }, null)
</li>
}
</ul>
}
else
{
<p><strong>ATTN!!</strong> We could not find any locations.</p>
}
When when rendered into the view, throws the following error upon running:
CS1973: 'System.Web.Mvc.HtmlHelper' has no applicable method named 'ActionLink' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
This happens on my action link.
I have seen other partials use action links, some with [] around the action links other with out. What am I doing wrong?

You have to cast item.name to a string, by the way if you don't use the htmlAttributes parameter, leave it instead of putting null for cleaner code

Related

Problems with RenderAction in MVC 3

I wanted use MVC and renderpartial to generate a menu but but could not get it to work, and from what I read it seemed maybe RenderAction would be more suitable. Still I have not gotten it to work.
What I intended to do was create a controller that selects certain articles from a database that will act as categories (this is put into HomeController):
public ActionResult MenuController()
{
var movies = from m in db.Art
where m.ArtikelNr.StartsWith("Webcat")
select m;
return View(movies);
}
And then send that information to a view:
#model IEnumerable<xxxx.Models.Art>
#{
Layout = null;
}
<ul>
#foreach (var item in Model)
{
<li>#Html.DisplayFor(modelItem => item.Benämning_10)</li>
}
This works when I just run it as a normal controller and view, it returns a list of what I want. But if I want to call it from _layout.cshtml (because this menu should appear on every page) like this:
<div id="sidebar">#Html.RenderAction(MenuController)</div>
Then it generates the following error:
CS0103: The name 'MenuController' does not exist in the current context
What is the proper way of calling an action/view/whatever from the _layout.cshtml file?
You should call
#Html.RenderAction("_MenuController")
and be sure that you have a working rule in your Global.asax
As suggested in another answer would be better to use
return PartialView();
I also suggest you to use the ChildActionOnlyAttribute to be sure that this action will never be called as a standard action.
So something like that:
[ChildActionOnly]
public PartialViewResult _MenuController()
{
var movies = from m in db.Art
where m.ArtikelNr.StartsWith("Webcat")
select m;
return PartialView(movies);
}
#{Html.RenderAction("MenuController");}
or
#Html.Action("MenuController")
Simply
#Html.RenderAction("MenuController")
You've forgotten quotes around your string parameter
<div id="sidebar">#Html.RenderAction("_MenuController")</div>
Quotes around your action name :) It might also be good practice to return a partial view:
return PartialView(movies);

Adding HtmlAttributes to template

If I am passing HtmlAttributes into a template, like this:
#Html.DisplayFor(m => m.FirstName, new { htmlAttributes = new { #class = "orangetxt strongtxt" } })
In my template, how would I inject these into my HTML:
<span #ViewData["htmlAttributes"]>#Model</span>
This almost works, but it does some pretty weird stuff, so I'm assuming this isn't the way to go.
I realize I can accomplish this with an HtmlHelper extension method to render the full HTML element (span, in this case) and pass in the attributes that way, but is there a way to just render attributes straight into an HTML element, like the above example?
The below extension method will allow me to convert HtmlAttributes to a string:
public static MvcHtmlString RenderHtmlAttributes<TModel>(
this HtmlHelper<TModel> htmlHelper, object htmlAttributes)
{
var attrbituesDictionary = new RouteValueDictionary(htmlAttributes);
return MvcHtmlString.Create(String.Join(" ",
attrbituesDictionary.Select(
item => String.Format("{0}=\"{1}\"", item.Key,
htmlHelper.Encode(item.Value)))));
}
Then, to render them within the tag, I can just do this:
<span #Html.RenderHtmlAttributes(ViewData["htmlAttributes"])>#Model</span>
Jerad Rose's answer is good, but I ran into couple of issues with it:
It does not not convert underscores to dashes in attribute names
It does not handle no-value attributes gracefully
To address first issue, use HtmlHelper.AnonymousObjectToHtmlAttributes.
Below is my modification of Jerad's method:
public static MvcHtmlString RenderHtmlAttributes(this HtmlHelper helper, object htmlAttributes)
{
if (htmlAttributes == null) return new MvcHtmlString(String.Empty);
var attrbituesDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
return new MvcHtmlString(String.Join(" ", attrbituesDictionary.Select(item => string.IsNullOrEmpty((string)item.Value) ? String.Format("{0}", item.Key) : String.Format("{0}=\"{1}\"", item.Key, helper.Encode(item.Value)))));
}
Try this instead,
#Html.DisplayFor(m => m.FirstName,
new { htmlAttributes = "class = orangetxt strongtxt"})
This will render a string, whereas your version did do weird stuff, rendered { } as part of the output.
DisplayFor() is used to render the template that matches the property type.
Display templates are .cshtml files inside /DisplayTemplates folder which in turn is inside a view folder (i.e. any folder from Home, Shared or even a specific controller).
An example.
If you've a String.cshtml template like this inside /Views/Shared:
#model String
#if (string.IsNullOrEmpty(Model)) {
<span>(no string)</span>
}
else {
<span>#Model</span>
}
Every time you call DisplayFor() for a string property:
DisplayFor(model => model.MyStringProperty);
It renders the template accordingly to the string's value. You can be more specific and put /DisplayTemplates inside a specific View folder and them only calls from those views are affected by the template.
In your case you can be even more specific and call DisplayFor() with a particular template.
Suppose you've a template for a particular property, called MyPropertyTemplate.cshtml. You would call DisplayFor() like this:
DisplayFor(model => model.MyProperty, "MyPropertyTemplate");
And them, inside that template you can have whatever HTML attributes you want.
#model MyProperty
<span class="orangetxt strongtxt">#MyProperty.ToString()</span>
PS: When it doesn't find a template I guess it only calls model.Property.ToString() without additional html.
FYI: EditorFor(), for example, works in a similar way but it uses /EditorTemplates folder.

No output from Html.ActionLink when trying to check Controller name

This is probably a simple oversight but I'm not seeing the problem so I thought I'd ask for some quick help. I'm somewhat new to MVC also (and Razor) so that might have something to do with it also. Basically, here's what's in my Razor View that renders some list items for a navigation bar. I'm just trying to set a class of "selected" on the element if (according to the Controller name) it's the page being requested.
<li>#{ if(Html.ViewContext.RouteData.Values["controller"].ToString() == "AdminHome")
{
Html.ActionLink("Admin Home", "Index", "AdminHome", null, new { #class = "selected" });
}
else{
Html.ActionLink("Admin Home", "Index", "AdminHome");
}
}
</li>
The result I'm getting is just an empty list item element: <li></li>
Any ideas what I'm doing wrong? Is it just a syntax issue?
You need to prefix # before Html.ActionLink. Otherwise MVC treats this as a ordinary method call not a method that outputs html.
<li>
#if(Html.ViewContext.RouteData.Values["controller"].ToString() == "AdminHome"){
#Html.ActionLink("Admin Home", "Index", "AdminHome", null, new { #class = "selected" });
}
else{
#Html.ActionLink("Admin Home", "Index", "AdminHome");
}
</li>
compare the route name in case insensitve like
String.Equals(Html.ViewContext.RouteData.Values["controller"].ToString() , "AdminHome",,StringComparison.CurrentCultureIgnoreCase)
because route name can be in any case. It is what user has typed in url.
For example in url
www.yourdomain.com/AdminHome/Index (controller name is AdminHome and controller will be AdminHomeController)
but with url
www.yourdomain.com/adminhome/index (controller name is adminhome and controller will be AdminHomeController)
Hope this helps.

Adding element if user is in role

I do something like that:
<ul id="menu">
#if (HttpContext.Current.User.IsInRole("admin")) {
<li>#Html.ActionLink("Administration", "Index", "Administration")</li>
}
</ul>
Is there a better way?
Daniel is correct that there's no way to make this shorter with existing helpers and methods, but if you're building a lot of menu links and they're all very similar you could write a small helper function:
#helper MenuLink(string linkText, string action = "Index", string controller = null)
{
<li>#Html.ActionLink(linkText, action, controller ?? linkText.Replace(" ", string.Empty))</li>
}
Now your code looks like:
<ul id="menu">
#if (HttpContext.Current.User.IsInRole("admin")) {
#MenuLink("Administration")
}
</ul>
Those defaults help make things shorter, but if you need a different action from Index or your controller name isn't the same as the link text without spaces, it's easy to override them:
#MenuLink("Admin reports", "Admin", "Reports")
Going a step further, there are a couple of extension methods I use that would make this even shorter:
public static class HtmlStringConditionalExtensions
{
public static IHtmlString If(this IHtmlString value, bool evaluation)
{
return evaluation ? value : MvcHtmlString.Empty;
}
public static IHtmlString ForRoles(this IHtmlString value, params string[] roles)
{
return value.If(roles.Any(HttpContext.Current.User.IsInRole));
}
}
And now you can write:
<ul id="menu">
#MenuLink("Administration").ForRoles("admin")
</ul>
This is of course the shortest way. Its seems you are building a global menu with links only visible for certain users. You should have a look at MvcContribs.UI.MenuBuilder namespace.
http://mvccontrib.codeplex.com/

MVC3 Ajax.BeginForm odd behavior

I have a very simple view that has a DropDownListFor and a Button inside an Ajax.BeginForm helper. Clicking the button renders the whole view again inside the div I have set to update including the layout page (I also notice a spike in the cpu when clicking the button multiple times)
Here is the Ajax.BeginForm inside the view:
#using (Ajax.BeginForm("About2", "Home", new AjaxOptions { UpdateTargetId = "property22" }))
{
<div>
<div id="property22">
#Html.DropDownListFor(m => m.SomePropertyToBind, new SelectList(Model.list, "property1", "property2"))
</div>
<button type="submit" id="test">
Click me</button>
</div>
}
Any ideas where I'm going wrong?
I uploaded the whole project if someone has a couple of minutes to take a look at it:
http://www.sendspace.com/file/siu3r31 (free provider so there may be a popup)
Thanks
You are using a wrong overload of the Ajax.BeginForm helper. It should be like this:
#using (Ajax.BeginForm(
"About2",
"Home",
null,
new AjaxOptions { UpdateTargetId = "property22" },
new { #id = "refreshe" }
))
Notice the additional null value I am passing as the routeValues parameter. Also in the example you uploaded you forgot to include the TestView.cshtml view. This being said in order to fix the problem you have two possibilities:
Either return a partial view:
public ActionResult About2()
{
Random randomizer = new Random();
int random = randomizer.Next(1, 1000000000);
ModelTest newModelTest = new ModelTest();
string[] stringList = new string[] { "Red", "Blue", "Green" };
newModelTest.list = from test in stringList
select new ModelTestList
{
property1 = test,
property2 = test
};
newModelTest.SomePropertyToBind = stringList[random % 2];
return PartialView("TestView", newModelTest);
}
or disable the layout in the TestView.cshtml view:
#{
Layout = null;
}
Unfortunately from your explanation above and from the code, I am not sure what you are trying to achieve. However, I think your most worry is about having Ajax working in your view.
In your About2 action method, you are trying to return a complete view which is TestView (in that case, it doesnt exist) and passing it the newModelTest view Model. I would advise changing to return either a PartialView or JsonResult.
For example, changing the return statement of About2 action method to
public ActionResult About2()
{
...
return Json(newModelTest);
}
or changing it to a return type to string and returning "TestResult"
public String About2()
{
...
return "TestResult";
}
or you could change the return statement to return a PartialView
Thanks for your replies.
I just realized that About2 should have returned the "About" view instead of the "TestView". I had tried creating a partial view with the Ajax.BeginForm code but I came across the same problem.
This is my first attempt at Ajax.BeginForm (so far I have always used jquery), and I was under the impression that it works in a similar fashion in the sense that by specifying the target id only the contents of that element will get updated, not that the target will actually get replaced by the whole response object.
Thanks for your help, not only did I get it to work, but I now understand how it should work.
I suspect that what's happening is that you're returning the a complete View (including the layout template) in the Ajax response. Try changing your "Home" controller "About2" action temporarily to the following:
public ContentResult About2() {
return Content("Hello World");
}
I tested this sample Action with your Razor markup and clicking the button properly replaced your dropdown list with "Hello World!".
If this is indeed what's happening, then you'll want to return a View from "About2" without the layout by declaring the following at the top of the View that you're returning.
#{
Layout = null;
}

Resources