Macro inside another macro - umbraco

I'm trying to put Macro inside another macro,
and I get Error message from Umbraco(exactly from the line that I put RenderMacro) :
An error occured
Cannot render a macro when there is no current PublishedContentRequest.
Exception Details
System.InvalidOperationException: Cannot render a macro when there is no current PublishedContentRequest.
The question: what I need to do to render macro inside another macro"?
this is the Imporant parts of code of The CTAMacro that contain RenderMacro that calls "StartPracticingButton":
#using System.Text.RegularExpressions;
#using System.Configuration;
#inherits Umbraco.Web.Macros.PartialViewMacroPage
#{
string CTATitle = (string)Model.MacroParameters["ctaTitle"];
string CTAContent = (string)Model.MacroParameters["ctaContent"];
string CTATestId = (string)Model.MacroParameters["ctaTestId"];
string fullProductButtonText = "";
string fullProductSku = "";
}
<div class="ctaFreeTest">
<div class="cta">
<div class="ctaTitle">
#CTATitle
</div>
{
<div class="productPageButtonContainer">
////////////////////////HERE COME THE RENDERMACRO THAT GIVES THE ERROR:
#Umbraco.RenderMacro("StartPracticingButton", new
ViewDataDictionary { { "nodeid", Model.Content.Id } })
</div>
}
</div>
</div>

You could probably do entirely without macros, but as a minimum I would replace the macro inside the macro ("StartPracticingButton") with a partial view instead. That should fix it, I think, without changing the way you use CTAMacro.
And as a bonus, a partial view inheriting from UmbracoViewPage should already have the Umbraco context so you don't have to pass along the node ID. You can just use Model.Content.Id directly inside the partial view.
So
Html.RenderPartial("StartPracticingButton")
should do it, given of course that you move the StartPracticingButton file from Views/MacroPartials to Views/Partials?

Related

RenderAction() to make decoupled components but how to communicate with other components

I have a busy view where most of the sections on there work off an ID. I'm looking for a more component way to handle each section so I'm using RenderAction() for each section where they have their own controllers. However I have a search section/"component" and when they put in a new Id and submit on that section/"component", I need a way for that to communicate to all the other RenderActions() that new Id so they can do their thing (query DB to get more info specific to that section).
My Search section would be something like:
public class SearchController : Controller
{
[HttpGet]
public ActionResult SearchContract()
{
var vm = new SearchVM();
return PartialView(vm);
}
[HttpPost]
public ActionResult SearchContract(SearchVM Search)
{
return PartialView(Search);
}
}
#using (Html.BeginForm())
{
<div class="row">
<div class="col-md-3">Contract Id</div>
<div class="col-md-6">
#Html.TextBoxFor(m => m.Id, new { #class = "form-control" })
</div>
</div>
<input type="submit" />
}
Let's say ContractHeader is a section/"component" using RenderAction() that hits a different controller and method from the search:
public class ContractController : Controller
{
public ActionResult ContractHeader(int ContractId)
{
// query contracts
return PartialView(vm);
}
}
Again, I'm looking for a more component oriented way with this. Yes it could all be in one controller but that's not what I'm looking for here. I want a more decoupled/compartmentalized approach to these areas on my views but trying to figure out how they can communicate with each other when "events" happen.
I think I have it figured out. Basically on each search "component" (I'm calling components a separate controller and view that you use RenderAction() to get on your main view) the method that gets called when the search button is pressed will return the following code (I subclassed Controller and put tis method in)
public ActionResult RedirectWithQueryString()
{
// get the referrer url without the old query string (which will be the main view)
var uri = new Uri(Request.UrlReferrer.ToString());
var url = Request.UrlReferrer.ToString().Replace(uri.Query, "");
var allQS = System.Web.HttpUtility.ParseQueryString(uri.Query);
var currentQS = System.Web.HttpUtility.ParseQueryString(Request.Url.Query);
var combinedQS = new NameValueCollection();
// update existing values
foreach (var key in allQS.AllKeys)
{
combinedQS.Add(key, allQS[key]);
}
// add new values
foreach (var key in currentQS.AllKeys)
{
if (combinedQS.AllKeys.Contains(key))
combinedQS[key] = currentQS[key];
else
combinedQS.Add(key, currentQS[key]);
}
var finalUrl = url + combinedQS.ToQueryString();
return Redirect(finalUrl);
}
public class ContractSearchController : MyBaseController
{
// GET: ContractSearch
public ActionResult Index(ContractSearchVM model)
{
return PartialView("ContractSearch", model);
}
public ActionResult SearchContracts(ContractSearchVM model)
{
return RedirectWithQueryString();
}
}
public class StopsSearchController : MyBaseController
{
public ActionResult Index(StopsSearchVM model)
{
// query to get some search related reference data like states list for drop down
return PartialView("StopsSearch", model);
}
public ActionResult SearchStops(StopsSearchVM model)
{
return RedirectWithQueryString();
}
}
SearchContracts() and SearchStops() methods are called from their own forms in their own views using HttpGet. In those methods then we are provided with just that forms query string but we also can get the UrlReferrer query string which will have other search forms key/values in it. So RedirectWithQueryString() basically makes sure the final query string has ALL keys required to satisfy the model binding of any search components on the view and will update the given keys with the current value for the current search component that the submit button was on.
So this then causes us to refresh to the current view with all current key/values in query string for all search components which then is calling all the RenderActions() and the values can be passed.
#model FocusFridayComponents.Models.CombinedVM
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-12">
#{ Html.RenderAction("Index", "ContractSearch"); }
</div>
</div>
<div class="row">
<div class="col-md-12">
#{ Html.RenderAction("Index", "StopsSearch"); }
</div>
</div>
</div>
<div class="col-md-6">
<!-- Contract Header -->
<div class="row">
<div class="col-md-12">
#{ Html.RenderAction("Header", "ContractHeader", new { ContractId = Model.ContractSearch.Id }); }
</div>
</div>
<!-- Contract Routes -->
<div class="row">
<div class="col-md-12">
#* #{ Html.RenderAction("Index", "ContractRoutes", new { ContractId = Model.Id }); } *#
</div>
</div>
</div>
In the main view you're working on you just make a VM that combines the search VM's you're using on the view. The model binding will correctly map the query string keys that match the search VM's even when they are inside the combined VM. The catch here would be to make sure the keys/props of each search VM don't share the same names of any kind.
What's interesting is for the RenderAction() for contract and stops search I don't need to pass the model into it. The binding just does this automatically. For ContractHeader and ContractRoutes I am passing in a parameter because the idea is those are separate components and have their own input requirements and those can be named completely separate from any search models you may be using in your view so the binding wouldn't be able to map anything. This is a good thing though as it decouples your actual view components from your search components.
So you would do all of this to get components that are decoupled from each other but can still talk to each other and you can assemble your views and reuse a lot of these components by just gluing the RenderAction() parameters between them. This can help reduce giant monolithic VM's that tend to pop up on complex views you're making.

UmbracoTemplatePage and strongly typed view not working together

I don't quite understand why I can't use both strongly typed view and umbraco template in one view.
I have a script importing excel data. My website is in umbraco 7.
I use Import.cshtml view which calls upload.cshtml partial
#inherits UmbracoTemplatePage
#{
Layout = "~/Views/Master.cshtml";
var locations = new List<SelectListItem>
{
new SelectListItem {Selected = false, Text = "location 1", Value = ""},
};
var fileTypes = new List<SelectListItem>
{
new SelectListItem {Selected = false, Text = "type 1", Value = ""},
};
}
<div id="page-bgtop">
<div class="post">
<h1>#Umbraco.Field("title")</h1>
<div class="entry">
#Html.Partial("Upload", new MvcImport.Models.ImportModel { FileTypes = fileTypes, Locations = locations })
</div>
</div>
<div class="clear"> </div>
</div>
Upload.cshtml partial does the job in ImportController. The data are there inserted into nicely into
List<CourseImport> courses = List<CourseImport>
All I want to do with it now is to display them in a view or partial view.
I tried several things:
CASE 1
1.
in ImportController I do:
return View("CoursesList", courses);
2.
The view includes a table which displays the rows and starts with
#inherits UmbracoTemplatePage
#model IEnumerable<MvcImport.Models.CourseImport>
#{
Layout = "~/Views/Master.cshtml";
}
3.
In this case I get:
Parser Error Message: The 'inherits' keyword is not allowed when a 'model' keyword is used.
CASE 2
1.
As 1. in Case 1.
2.
The view starts with (so without #inherits UmbracoTemplatePage)
#model IEnumerable<MvcImport.Models.CourseImport>
#{
Layout = "~/Views/Master.cshtml";
}
3.
I get:
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[MvcImport.Models.CourseImport]', but this dictionary requires a model item of type 'Umbraco.Web.Models.RenderModel'.
CASE 3
1.
In ImportController I do
return PartialView("CoursesList", courses);
2.
I'm now using partial view which it similar to the view in the above points and also is strongly typed eg.
#model IEnumerable<MvcImport.Models.CourseImport>
3.
In this case the data is displayed but without my umbraco template shape, no styling etc.
If in the partial view I include
#{
Layout = "~/Views/Master.cshtml";
}
I get the same error as in Case 2
Changing the starting directive to
#model UmbracoViewPage<MvcImport.Models.CourseImport>
does not work either as the model must be enumerable
Can someone advise me how to do it? I guess there must be a solution but I am very new to MVC so I don't get how things work yet too much.
Thanks.
The solution is provided on umbraco forum website http://our.umbraco.org/forum/developers/razor/55389-UmbracoTemplatePage-and-strongly-typed-view-not-working-together?p=0

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.

How to render HtmlAttributes with object values using ASP.NET MVC 3 Razor?

I am trying to render the following HTML using an MVC3 Razor View:
<input id="EffectiveDate" name="EffectiveDate" type="date" data-options='{"mode": "flipbox"}' />
I have been unable to get the quotation marks in the data-options attribute to render. No matter what I try, they are rendered as "
Here are a couple of the many approaches I have tried in my View:
#Html.TextBoxFor(model => model.EffectiveDate, new { type = "date", data_options= " { 'mode':'flipbox' }"})
and
#Html.TextBoxFor(model => model.EffectiveDate, new { type = "date", data_options= #Html.Raw("{\"mode\":\"flipbox\"}")})
Any suggestions on how to decode the quotation marks?
You can do this by creating an MVC Editor template. First, create a folder called "EditorTemplates" inside the "Views\Shared" folder. Then put a file called DateTime.cshtml inside the EditorTemplates folder.
Then you can simply use the EditorFor() method against your view model's property like this (provided that the EffectiveDate property of of type DateTime):
#Html.EditorFor(m => m.EffectiveDate)
The complete code for the DateTime.cshtml editor template looks like this:
#model System.DateTime
#{
var id = this.ViewData.TemplateInfo.GetFullHtmlFieldId("");
var name = this.ViewData.TemplateInfo.GetFullHtmlFieldName("");
}
<input id="#id" name="#name" type="date" data-options='{"mode": "flipbox"}' />
This will produce the exact output that you are seeking.
One thing is certain: special symbols will always be encoded when you use any of the default MVC input extensions (i.e. TextBoxFor). That is because TagBuilder itself, which is used to build the tags for the HtmlHelper extensions, HtmlEncodes each attribute value in a tag. You can see this in the TagBuilder source:
private void AppendAttributes(StringBuilder sb)
{
foreach (var attribute in Attributes)
{
string key = attribute.Key;
if (String.Equals(key, "id", StringComparison.Ordinal /* case-sensitive */) && String.IsNullOrEmpty(attribute.Value))
{
continue; // DevDiv Bugs #227595: don't output empty IDs
}
string value = HttpUtility.HtmlAttributeEncode(attribute.Value);
sb.Append(' ')
.Append(key)
.Append("=\"")
.Append(value)
.Append('"');
}
}
Since you have no way to send that value already decoded, you have to decode it yourself in JavaScript. Here is a nice little jQuery trick that will do it:
var value = $('<textarea/>').html(yourElement.data('options')).val();
You might want to make a function for that, of course.
Sources:
http://aspnet.codeplex.com/
http://refresh-sf.com/blog/2009/05/decode-html-entities-with-jquery/

Conditional ASP.NET MVC razor sections

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")
}

Resources