Conditional ASP.NET MVC razor sections - asp.net-mvc

I want to define this section only if some property (Model.ReadOnly) is false.
#section toolbar {
<div class="tool">
<div class="row">
#Html.ActionLink( Resources.Strings.Edit, "Edit", "Profile" )
</div>
<div class="row">
#Html.ActionLink( Resources.Strings.Delete, "Delete", "Profile" )
</div>
</div >
}
I tried wrapping it up in #if ( !Model.ReadOnly ) {} but it doesn't work.
Is there a way to do this?
I do not want to define an empty section (as #itsmatt suggests), the layout of my page changes whether the section is defined or not (using IsSectionDefined( "toolbar" )).

This should work.
#if (!Model.ReadOnly)
{
<text>
#section toolbar {
}
</text>
}
I never said it would be pretty ;-)

This works for me:
#section SomeSection {
#if (!Model.ReadOnly)
{
}
}
Essentially flipping where the conditional is. This essentially results in an empty section if Model.ReadOnly is true.
Update:
So, what about moving that section to a PartialView and doing something like:
#Html.Partial("MyAction")
in your View and then let the MyAction return you the appropriate PartialView based on the ReadOnly value? Something like:
public PartialViewResult MyAction()
{
...
// determine readonly status - could have passed this to the action I suppose
if (ReadOnly)
{
return PartialView("TheOneThatDefinesTheSection");
}
else
{
return PartialView("TheOneThatDoesNotDefineTheSection");
}
}
Seems like that would work just fine.

Bertrand,
See:
Razor If/Else conditional operator syntax
basically (para-phrasing), ... Razor currently supports a subset of C# expressions without using #() and unfortunately, ternary operators are not part of that set.
also, this may be a way around the issue:
conditional logic in mvc view vs htmlhelper vs action
basically, use the if logic to call a partialview to satisfy your criteria.
[edit] this was my basic thinking (where your #section code is defined in that partial):
#if(!Model.ReadOnly)
{
#Html.Partial("toolbar")
}

Related

Is it bad practice to have logic in view

I have a table and each item on it has a status or state and depending on this status you can either buy an item or set it in a waiting list.
To do that I have an #if statement in my Razor view where I check the status and then conditionally choose which button to render for the appropriate actions.
I have some similar logic in other views for sessions, and roles.
Is it a good practice? Are there other ways to do it?
#if( #item.status == 1 ) {
<a class="btn btn-default" href="Items/Buy/#item.id">Buy</a>
} else if ( item.status == 2 ) {
<a class="btn btn-default" href="Items/SetList/#item.id">Add To List</a>
}
It is not the worst thing in the world, since it is being used to display elements in the view. However I would make an extension method for the HtmlHelper to render the action links you are trying to create. Something like:
public static class HtmlHelperExtensions
{
public static HtmlString RenderStatus(this HtmlHelper helper, ItemModel item)
{
if (item.status == 1)
{
return helper.ActionLink("Buy", "Buy", "Items", new { #class = "btn btn-default" });
}
if (item.status == 2)
{
return helper.ActionLink("Add To List", "SetList", "Items", new { id = item.Id }, new { #class = "btn btn-default" });
}
return new MvcHtmlString("");
}
}
Then in your view, all you would need to do is call
#Html.RenderStatus(item)
There isn't anything wrong with the way you've done this.
When you hear or read about the idea that it is 'bad practice to include logic in your views' it is typically referring to business logic.
There is nothing that prevents one from passing a model into a view that has all kinds of methods available on it, that can then be executed from code in a view -(but this is what you should avoid -- instead getting this done in the controller action).
The logic that you have shown is really rendering logic -- you are conditionally choosing the appropriate HTML to include in your output. No problem at all in my opinion.
If this logic gets too cumbersome in one or more views you can use HTML helpers, PartialViews, and functions in Razor to help with that problem.
While I fundamentally agree with the fact that what you're doing is technically rendering logic, determining whether or not something can be bought or added could be considered business logic.
Also, you might have items other than the buttons that depend on that logic to be displayed or active or something.
I would suggest the following approach:
Define one or more bool properties on your view, such as CanBuy.
The value of the property can be set in the controller or in the property definition in the view, like so
public bool CanBuy
{
get { return (status == 1); }
}
Then in your view you would simplify the if statement like this:
#if(#item.CanBuy ) {
<a class="btn btn-default" href="Items/Buy/#item.id">Buy</a>
} else if ( item.CanAdd ) {
<a class="btn btn-default" href="Items/SetList/#item.id">Add To List</a>
}
In addition, Bobby Caldwell's suggestion to abstract this in a HtmlHelper is valuable and could be combined with this method.

MVC Entity Framework Razor Issue

I have to following code and there's a problem and I can't figure out what's the problem. Well, I have two ideas about the place where the problem may appear but I can't find a solution.
The problem is that my layout is rendered twice.
Layout.cshtml
<div id="container">
<div id="left_side"> #RenderPage("left_side.cshtml") </div>
<div id="center_side"> #RenderBody() </div>
<div id="right_side"> #RenderPage("right_side.cshtml") </div>
</div>
left_side.cshtml
#if (ViewBag.LeftColumnVisible == true)
{
#Html.Action("GetCategories", "Products");
}
GetCategories method from Products controller
public ActionResult GetCategories()
{
List<Categories> categories = db.Categories.ToList();
...
return View();
}
GetCategories.cshtml
#foreach (System.Collections.DictionaryEntry de in ViewBag.LeftColumnContent)
{
<div> #((Ads.Models.Categories)(de.Key)).Name; </div>
}
It enters the get categories and renders the content.
The problem for rendering twice may be at this line
#Html.Action("GetCategories", "Products"); or when it calls View(). If I comment the line, my layout will be rendered only once.
I'd say there are a couple of issues with your code. First of all, if left_side.cshtml/right_side.cshtml are partial views then you want to be using
#Html.RenderPartial("view")
to render them and not #RenderPage - although #RenderPage will work it's better from a readability point of view to understand exactly what the type of view it is you are working with.
Secondly, if your GetCategories view is a partial view you want to be returning a PartialView and not a View i.e.
public ActionResult GetCategories()
{
...
return PartialView();
}

Partial view - avoiding nested forms

View
#using (Html.BeginForm())
{
...form elements
#Html.Action("PartialView")
}
PartialView
if (something) {
<input type="submit" value="Submit" />
} else {
#using (Html.BeginForm())
{
<input type="submit" value="Submit" />
}
Can anybody suggest a way around the above problem?
If the PartialView if statement returns false, I end up with nested forms. I can move the form close bracket within the partial view to avoid nesting the forms and the page renders correctly but this upsets visual studio because it expects to see the close bracket within the view. Does that matter?
Edit:
Based on Chris's comments, is the below modification a better approach? i.e. One form with two submit buttons that call different code within the same action method?
PartialView
if (something) {
<input type="submit" name="btn" value="Submit1" />
} else {
<input type="submit" name="btn" value="Submit2" />
}
Controller
[HttpPost]
public ActionResult Index()
{
if (btn == "Submit1") {
...do a thing
} else {
...do another thing
};
}
<form> tag inside another <form> is not a valid HTML
Refer W3c Spec
Workaround available
http://blog.avirtualhome.com/how-to-create-nested-forms/
I run into the same problem, and came up with a helper that really solves it.
/**
* Ensure consequent calls to Html.BeginForm are ignored. This is particularly useful
* on reusable nested components where both a parent and a child begin a form.
* When nested, the child form shouldn't be printed.
*/
public static class SingleFormExtensions
{
public static IDisposable BeginSingleForm(this HtmlHelper html)
{
return new SingleForm(html);
}
public class SingleForm: IDisposable
{
// The form, if it was needed
private MvcForm _form;
public SingleForm(HtmlHelper html)
{
// single per http request
if (!HttpContext.Current.Items.Contains(typeof(SingleForm).FullName))
{
_form = html.BeginForm();
HttpContext.Current.Items[typeof(SingleForm).FullName] = true; // we need the key added, value is a dummy
}
}
public void Dispose()
{
// close the form if it was opened
if (_form != null)
{
_form.EndForm();
HttpContext.Current.Items.Remove(typeof(SingleForm).FullName);
}
}
}
}
To use it, include the namespace of the extension and do #Html.BeginSingleForm( everywhere you want. Not just inside nested views, but also in the parent.
Points of interest: There is a need to save whether or not the form has opened earlier. We can't have a static or static per thread variable ThreadStatic, as this might be used by many Asp.Net threads. The only single-threaded and per-http-request place to add a variable is the HttpContext.Current.Items dictionary.
There is no limitation to the number of submit buttons. The problem is the nested form elements. To avoid having multiple submit buttons you can either hide them using jquery, or extend this helper to automatically add a submit button at the end.

Looking for alternate ways to create blocks of HTML code in ASP.NET MVC 3

I'm not sure how to phrase my question, so I essentially want to be able to do something like this, in ASP.NET MVC 3:
#myJsHtmlCustomCode
{
<div><h1>Title</h1></div>
}
where anything in that myJsHtmlCustomCode block would be surrounded with some custom JavaScript/HTML code that I wrote.
I know I could use something like myJsHtmlCustomCode.Begin() and myJsHtmlCustomCode.End() in my code, but that doesn't provide the same formatting structure.
If anyone knows of a similar way to achieve the same objective, and get the automatic outlining/indent formatting, that would be great.
Just for reference, I wanted the the #myJsHtmlCustomCode to surround the code with for instance, another <div id="myTestId" onclick="thisClickEvent"></div> for the resulting code to look like...
<div id="myTestId" onclick="thisClickEvent">
<div><h1>Title</h1></div>
</div>
Option 1
You can wrap your code in an object that implements IDisposable, just like you use #using (Html.BeginForm())
Your code could be like this:
public class MyJsHtmlCustomCode : IDisposable {
private ViewContext _ctx;
public MyJsHtmlCustomCode (HtmlHelper html, /* other params */) {
_ctx = html.ViewContext;
/* Write begin tags */
_ctx.Writer.Write("html => opening tags"); }
public Dispose() {
_ctx.Writer.Write("html => closing tags");
}
}
and the extension:
public static MyJsHtmlCustomCode BeginMyJsHtmlCustomCode(this HtmlHelper html /* other params */) {
var result = new MyJsHtmlCustomCode(html);
return result;
}
Usage:
#using(Html.BeginMyMyJsHtmlCustomCode()) {
<div>This is surrounded by your code </div>
}
Option 2
You can use Razor Templated Delegates:
#{
Func<dynamic, object> b = #<strong>#item</strong>;
}
<span>This sentence is #b("In Bold").</span>
<div>#b(
#<span>
#DateTime.Now
</span><span>Example of more complex logic
</span>
)</div>
While I must admit I do not completely understand your question, whenever I need to do programatic custom HTML manipulation in .Net MVC 3 I use the Html Agility Pack.
Could you achieve your desired result using #section ?
e.g.: in one cshtml, possibly the _Layout:
<script type="text/javascript">
// whatever you want
// more whatever you want
#RenderSection("jsCode", false)
// even more whatever you want
</script>
Then in your actual view cshtml:
#section jsCode{
<div><h1>Title</h1></div>
}
You could possibly use a partial view for the js bit e.g.

Are lambda expressions supported by Razor?

Are lambda expressions/anonymous methods supported in the Razor view engine?
I am having difficulty expressing the following in Razor:
#Model.ToList().ForEach(i =>
{
if (i.DealerName != null)
{
<text>
#i.DealerName
</text>
}
}
Note: I know can solve this with #foreach but I need a similar solution for a 3rd party MVC control. It using this mechanism for setting the content of the control. It works fine for MVC .ASPX views but cannot get it to work with Razor.
MVC .ASPX equivalent (the code I would like to convert to Razor syntax):
<% Model.ToList().ForEach(i =>
{
if (i.DealerName != null)
{
%> <%=i.DealerName%> <%
};
});
%>
This is for the Razor engine that ships with ASP.NET MVC3.
Instead of your <text>#i.DealerName</text> block you could use a Response.Write(i.DealerName);
The result is the same, as if you drop this in a Razor page - it will execute while rendering page.. And frankly - I'm pretty sure this is what it will be compiled into anyway.
Also, since ForEach() returns void, you'd have to drop it in the page as a code block.
So your code would look something like this:
#{
Model.ToList().ForEach(i =>
{
if (i.DealerName != null)
{
Response.Write(i.DealerName);
}
});
}
UPD: If you have more serious formatting, you can resort to this nice little trick:
(unfortunately the code colouring here will not give this snippet any credit, but you'll definitely see what I mean if you drop this in visual studio. Note: this will only work in Razor pages, not code files :) )
#{
Model.ToList().ForEach(i =>
{
if (i.DealerName != null)
{
Response.Write(((Func<dynamic, object>)(
#<text>
<b>Hello Dealer named: #item.DealerName
Multiline support is <em>Beautiful!</em>
</text>)).Invoke(i));
}
});
}
Hope that makes sense :)
Alternatively, you can create a lambda function, and call that for each item in the body of your Razor code (the idea came from Andy in this post):
#model IEnumerable<Dealer>
#{
Func<Dealer, object> sayHi =
#<text>
<b>Hello Dealer named: #(item.DealerName)</b>
Multiline support is <em>Beautiful!</em>
</text>;
}
<div>
#foreach(var dealer in Model.ToList())
{
sayHi(dealer);
}
</div>
Yes, they are supported. BUT, Razor has some weird escaping rules and extra braces will cause it to choke sometimes, including those in extended lambda expressions.
You can simplify the #Artioms answer a bit to remove those extra braces with a where and optionally a select clause
#{
Model.ToList().ForEach(i =>
{
if (i.DealerName != null)
{
Response.Write(i.DealerName);
}
});
}
becomes
#{
Model.Where(i=>i.DealerName != null).ToList().ForEach(i =>
{
Response.Write(i.DealerName);
});
}
Could also become
#{Model.Where(i=>i.DealerName != null).Select(i=>i.DealerName)
.ToList().ForEach(Response.Write);}
Yay functional styles!

Resources