Why does Umbraco treat IPublishedContent differently when called via AJAX - c#

The short story is I have a list of blog articles which I'm casting to a model which contains .wordCount. This is calculated by striping the HTML from the output of the RTE and then calculating the lengh. The output of the RTE can contain Macros.
On first load, i.e from a view, the output of the RTE renders my Macros as HTML. However, when I refresh my list articles using a clientside AJAX the output of the RTE is rendered differently. My Macros now look like this: <?UMBRACO_MACRO macroAlias="ArticleAudio" audioPicker="6068" audioPosition="left" audioTitle="Interview Audio" />
I also get the error System.InvalidOperationException: 'Cannot render a macro when there is no current PublishedContentRequest.
public static Article ToArticle(this IPublishedContent item)
{
string rawText = HelperFunctions.StripHTML(item.GetPropertyValue<string>("richText"));
...
}
public static string StripHTML(string htmlString)
{
string pattern = #"<(.|\n)*?>";
return Regex.Replace(htmlString, pattern, string.Empty);
}
Expected
<div class="audio-player">
<audio style="display: none;">
<source src="https://www.address.com/media/77390/interview.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</div>
My Output
<?UMBRACO_MACRO macroAlias="ArticleAudio" audioPicker="6068" audioPosition="left" audioTitle="Interview Audio" />
Thank you in advance for your help

When you get the property value, it will have the placeholder string for the macro in it. There is some code that runs as part of rendering the page that turns those into the actual Macros. I can't remember off the top of my head where that lives though.
Can you post the full code of the controller you are calling to update via AJAX? Is it a surface controller, an API controller, or something different?

Related

Blazor Dynamic Custom Elements in application

I'm developing a webapp built in C# with Blazor WASM that is Asp.Net hosted. I'm making a blazor component that through the use of a library already in production, will generate a HTML fragment (or full embed) that is then displayed in this way
...
<div>
#((MarkupString)document)
</div>
...
with document containing the markup generated by the library.
As long as we're doing it with static content all is fine and dandy, but now we need to have some input in there that will then be sent back to the server to execute some actions.
In a MarkupString there is no way to include <InputFile /> or <InputText /> components in such a way that they are shown in the fragment and I can read their contents, and I can find no way to actually interact with the standard HTML tags, especially regarding the file upload.
Moreover we'll probably soon need to have a specific image uploader with preview which would be a custom Blazor component and this led me to the CustomElements .NET 7 feature that looks like what I need for both problems.
However I couldn't find how to actually implement this in my app, and the documentation I found is still very partial in that way. Is there a way to do what I need?
EDIT: Managed to fix this partially, with Chen's answer. I still have trouble with the binding though, as the #bind-Value directive is not working with
Unhandled exception rendering component: Microsoft.AspNetCore.Components.Forms.InputText requires a value for the 'ValueExpression' parameter. Normally this is provided automatically when using 'bind-Value'.
with this markup:
...
<custom-input-text #bind-value="$field1" name="$field1"></custom-input-text>
...
(the capital V in bind-Value becomes lowercase all by itself)
Am I doing something wrong again?
CustomElements should meet your requirements, you can create your own logic in Blazor components, and then use it in your application.
To use the component, you need to add the following JavaScript script references to your host app in this specific order.
<script src="_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
You also need to add the corresponding middleware:
app.UseBlazorFrameworkFiles();
And use app.UseWebAssemblyDebugging(); for debugging.
Then you need to register the corresponding component in the Blazor program:
builder.RootComponents.RegisterCustomElement<Counter>("my-counter");
Then you can call this component in your application, including passing parameters, etc.
<my-counter title="Khalid" increment-amount="2" />
Here is a complete example with detailed explanation, you can use it as a reference.
Helpful links:
Blazor Custom Elements.
Using .NET 7's Blazor Custom Elements to render dynamic content.
ASP.NET Core Razor components.
Hope this can help you.
Edit1:
It looks like you can't do two-way binding between Razor Page and Razor Component. The official document mentions that parameters can be passed through JavaScript properties, but it doesn't seem to be able to read the parameters.
For example:
<my-counter ></my-counter>
<button onclick="Test()">click</button>
<script>
function Test()
{
const elem = document.querySelector("my-counter");
//get parameter failed, it's undefined
var text = elem.incrementAmount;
//successfully set parameter
elem.incrementAmount = "test";
}
</script>
So I'm guessing that CustomElements only have writable properties in Razor Page.
Edit2:
I found that JQuery can be used to detect the value of the input box. When getting this value and performing certain operations, can it also achieve the same effect as two-way binding?
<my-counter ></my-counter>
<button onclick="Test()">click</button>
<script>
function Test()
{
const elem = document.querySelector("my-counter");
//get the value of input
var value = $("my-counter").find("input").val();
elem.incrementAmount = "test";
}
</script>

Convert Blazor HTML markup portion to string for email body

I have a simple Blazor component that take some inputs (some lists of object and few strings) and formats them into simple HTML for display, (tables generated from the lists of objects, simple text, etc.).
This HTML is a report that is intended to be both displayed to users in the app and also emailed to various people (via SendGrid). For compatibility, we are keeping the email HTML as simple as possible.
The component works fine, however I am not sure how to translate a component's markup portion into a simple string of escaped HTML so that I can pass the string to SendGrid and fire off an email.
I am aware of MarkupStrings, but I have only used them in reverse--to write a string containing HTML tags that will be properly displayed in my app. I can't find any suggestions for doing the conversion the way that I need it done.
Is there any simple way to have a component write all of its markup into a string so that I can email it out?
Or, would I be better off writing a .cs file with a static method that takes in the parameters in question, renders it into a MarkupString, and then passes the string both to SendGrid for email and also to a Blazor component for in-app display?
The simplest way to do that is to employ JSInterop to retrieve the Html markup for the component, produced by the browser. Let's say You've defined a child component, and you want to retrieve its html source. You can do that like this:
Define the child...
SelectGender.razor
<div id="selectGender">
<h1>Select Gender</h1>
<select>
#foreach (var gender in genders)
{
<option>#gender</option>
}
</select>
</div>
#code {
private List<string> genders = new List<string> { "Male", "Female", "Other" };
}
Usage
#page "/"
#inject IJSRuntime JSRuntime
<div>#((MarkupString) html)</div>
<SelectGender />
<button #onclick="GetHtml">Get Html</button>
#code{
private string html;
protected async Task GetHtml()
{
html = await JSRuntime.InvokeAsync<string>("myJsFunctions.getHtml");
}
}
_Host.cshtml
<script>
window.myJsFunctions =
{
getHtml: function () {
return document.getElementById("selectGender").innerHTML;
}
};
</script>
Is there any simple way to have a component write all of its markup into a string so that I can email it out?
No, your C# code has no simple way to do this - you could use JS Interop to get the rendered HTML from the dom, but nothing built in for it.
Or, would I be better off writing a .cs file with a static method that takes in the parameters in question, renders it into a MarkupString, and then passes the string both to SendGrid for email and also to a Blazor component for in-app display?
That is a possibility - I can't comment on the value of that to you, but it is a technique that could work if the component you are rendering is static,

Rendering a display name containing HTML that's stored in Resource file

I need to render a HTML link that exists inside of a resource file
[Display(Name = "MyPropertyName" ResourceType = typeof(Res.Props))]
public bool MyProperty{ get; set; }
The MyPropertyName resource has some HTML:
"Click here to find out more."
I need to render this on the View so the link is clickable. The HTML will never render despite what method I attempt, some attempts (not exact syntax but you get the gist) will display the raw HTML, and some don't display anything at all.
var htmlLabel = Html.LabelFor(m => m."MyPropertyName" ).ToString()
Html.Raw(htmlLabel)
Html.Raw(Html.LabelFor(m => m."MyPropertyName" ).ToString())
Html.Raw(Html.LabelFor(m => m."MyPropertyName")
MvcHtmlString.Create(htmlLabel);
Html.Raw(MvcHtmlString.Create(htmlLabel))
etc.
I found a suggestion of getting the attribute info on the controller and passing it in via ViewBag and then rendering it with HTML.Raw, So I followed this here (and a a few others) but that code only pulls in the name of the property "MyPropertyName" and not string from the resource file.
Any ideas? Thanks.
well now this suddenly decided to work after one last silly attempt. For anyone that hits this kind of issue in the future:
The application has a StronglyTypedResourceBuilder class that contains all of the data I need. So the following works without trouble:
#using StringResources = Namespace.Resources.StringResources
...
#Html.CheckBoxFor(m => m.MyPropertyName)
#Html.Raw(#StringRes.MyPropertyName)
...
what a waste of a morning.

How to pass data from asp.NET MVC to Angular2

What is the best way to pass data from an ASP.NET MVC controller to an Angular 2.0 component? For example, we use the ASP.NET MVC Model and would like to send a JSON version of it to Angular to use it in Angular.
When the controller is serving the view, we can already push some data to Angular2 (the model). So additional AJAX call to fetch that data is not required.
However, I am struggling to "inject" it into the Angular component. How do you do this? Any good references for this? As you may have noticed, I'm quite new to Angular2.
My index.cshtml looks like this.
<div class="container">
<div>
<h1>Welcome to the Angular 2 components!</h1>
</div>
<div class="row">
<MyAngular2Component>
<div class="alert alert-info" role="alert">
<h3>Loading...</h3>
</div>
</MyAngular2Component>
</div>
</div>
Kind regards,
Rob
The best way that I have found to pass data in from MVC (or any hosting/startup page) is to specify the data as an attribute on the bootstrapped component, and use ElementRef to retrieve the value directly.
Below is an example for MVC derived from the this answer, which states that it is not possible to use #Input for root-level components.
Example:
//index.cshtml
<my-app username="#ViewBag.UserName">
<i class="fa fa-circle-o-notch fa-spin"></i>Loading...
</my-app>
//app.component.ts
import {Component, Input, ElementRef} from '#angular/core';
#Component({
selector: 'my-app',
template: '<div> username: <span>{{username}}</span> </div>'
})
export class AppComponent {
constructor(private elementRef: ElementRef) {}
username: string = this.elementRef.nativeElement.getAttribute('username')
}
If you want to retrieve more complex data that is not suitable for an attribute, you can use the same technique, just put the data in a HTML <input type='hidden'/> element or a hidden <code> element, and use plain JS to retrieve the value.
myJson: string = document.getElementById("myJson").value
Warning: Access the DOM directly from a data-bound application such as Angular, breaks the data-binding pattern, and should be used with caution.
You might want to look for similar questions related to AngularJS, not Angular 2 specific, as the main gist of the thing remains the same:
you want your server-side Razor engine to render some kind of view (i.e. HTML or JS directly)
this view contains a JS template where part of the content is filled from a server model instance or anyway server data (e.g. a resource, dictionary, etc.)
in order to properly fill a JS variable from Razor, C# server-side data has to be properly serialized into a JSON format
In this post by Marius Schulz you can see as he serializes the data and uses that to fill a template AngularJS value component:
<script>
angular.module("hobbitModule").value("companionship", #Html.Raw(Model));
</script>
Something similar could be made to inject some data e.g. into window.myNamespace.myServerData, and then have Angular2 bootstrap that value among other providers.
In this post by Roel van Lisdonk, a similar approach is used, again, to fill an AngularJS-based template, with that ng-init directive:
<div ng-controller="main"
ng-init="resources={ textFromResource: '#WebApplication1.Properties.Resources.TextFromResource'}">
<h1>{{ title }}</h1>
{{ resources.textFromResource }}
</div>
As the first post points out, there's something to think about (pasting here):
A word of caution: The method I'm about to use is probably not a good fit for large amounts of data. Since the JavaScript data is inlined into the HTML response, it's sent over the wire every single time you request that page.
Also, if the data is specific to the authenticated user, the response can't be cached and delivered to different users anymore. Please keep that in mind when considering to bootstrap your Angular Apps with .NET data this way.
The second part may be less of an issue if your served page is already dynamic server-side, i.e. if it already has bits filled in out of server-side data.
HTH
You need to first bundle your services and controllers in separate module files and load services before controllers.
For example:
dist
|--services.js
|--controllers.js
Then you need to load the JavaScript code of the Services via ASP.NET MVC JavaScript result, here you need to inject your startup data.
public class ScriptController: Controller
{
public ActionResult GetServices(){
string file= File.ReadAllText(Server.MapPath("~dist/services.js"));
//modify the file to inject data or
var result = new JavaScriptResult();
result.Script = file;
return result;
}
Then in the index.html load the scripts as follows
<script src="/script/getservices"></script>
<script src="/dist/controller.js"></script>
This way you can inject data into angular code while loading.
However, even this has a performance impact due to time spent on fetching the view, compiling the view, and binding data to the view. For an initial load performance can still be improved if you use Server Side Rendering.
You can use the Input function exposed by #angular/core, I have for example an Angular 2 component to display information messages to the user of the application
My HTML template take an Angular 2 Message property
<div class="alert alert-info col-lg-10 col-lg-offset-1 col-md-10 col-md-offset-1 col-sm-10 col-sm-offset-1 col-xs-10 col-xs-offset-1">
<i class="fa fa-info-circle"></i> {{ Message }}
</div>
The Message property is passed as an input to my Angular 2 component named informationmessage.component.ts, for example
import { Component, Input } from '#angular/core';
#Component({
selector: 'informationmessage',
templateUrl: '../Templates/informationmessage.component.html'
})
export class InformationMessageComponent {
#Input() Message: string;
}
I then pass the data to my InformationMessageComponent using property binding in the HTML page, for example.
<informationmessage [Message]="InformationMessage"></informationmessage>
You can replace InformationMessage in the above example with the data that you get from your MVC controller for example
<informationmessage [Message]="#Model.InformationMessage"></informationmessage>
Please note: I did not test this scenario, but there is no technical reason for it not working, at the end of the day you are just binding a value to an Angular 2 property.
src/ApplicationConfiguration.ts
export class ApplicationConfiguration {
public setting1: string;
public setting2: string;
}
src/main.ts
declare var config : ApplicationConfiguration;
var providers = [{ provide: ApplicationConfiguration, useValue: config }];
platformBrowserDynamic(providers).bootstrapModule(AppModule)
.catch(err => console.log(err));
src/index.html
<script type="text/javascript">
var config = {setting1: "value1", setting2: "value2"};
</script>
src/app/app.component.ts
export class AppComponent {
private _config : ApplicationConfiguration;
constructor(config: ApplicationConfiguration) {
this._config = config;
}
}
I found a much simpler solution. Don't attempt to get the attribute from in the constructor! Use the ngOnInit() hook instead. The property will be accessible as long as it has been decorated with #Input(). It just appears that it is not available by the time the constructor is called.
Component HTML:
<MyComponent [CustomAttribute]="hello"></MyComponent>
Component TS:
export class MyComponentComponent {
#Input()
public CustomAttribute: string;
constructor() {}
ngOnInit() {
console.log("Got it! " + this.CustomAttribute);
}
}
Further to #Joseph Gabriel's approach, if you want a complex object to be passed via an attribute, in MVC you should serialize it to a string, and then in the angular side de-serialize it using JSON.Parse.

MVC Helpers Working inline, but not in App_Code

Originally I repeated lines of code for each menu item and just hard coded the various menu item values but then I came across helpers and taught I would give it a try. Now 6 lines of code (for each menu item) are reduced to one (for each item), and I have a single place to go to alter anything instead of changing it in 5 places. All great stuff. Here is the code:
#helper MenuItem(string action, string controller)
{
<a href="#Url.Action(action, controller)" id="#controller">
<div class="MenuItem">
<img src="#("/XXX.YYY.Web/Content/Images/Icons/Menu/mnu"+controller+".png")" /><br />
//I had to put the XXX.YYY as a literal string because the ~ didn't work, it was quoted literally also instead of showing the home folder.
#controller
</div>
</a>
}
My problem is that it works when I use it inline, say at the top of my _Layout.cshtml with the following lines of code:
#MenuItem("Index", "Home")
#MenuItem("Index", "Chart")
But when I remove it out to a generic helper called LayoutHelpers.cshtml under the App_Code folder so I can reuse it, and alter the code accordingly as follows:
#LayoutHelpers.MenuItem("Index", "Home")
#LayoutHelpers.MenuItem("Index", "Chart")
Note: Nothing in the actual helper changed. Only the above 2 lines in the _Layout.cshtml file changed.
When I make those changes I get the following error:
Compilation Error
Description: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately.
Compiler Error Message: CS0103: The name 'Url' does not exist in the current context
Source Error:
Line 3: #helper MenuItem(string action, string controller)
Line 4: {
Line 5:
Line 6:
Line 7: ##
Now the curious thing is, notice how it works on line 7 "mnuHome.png" as opposed to mnucontroller.png. Yet it says that line 5 is in error.
I also have a problem with the ~ not working in the helper. ie. the ~/Content is shown as a literal string instead of it being compiled to a proper path which should always point to the home folder of the app.
Following is a link that I am using for reference:
http://weblogs.asp.net/jgalloway/archive/2011/03/23/comparing-mvc-3-helpers-using-extension-methods-and-declarative-razor-helper.aspx
Specifically less than 1/4 of the way down the page under the heading "Razor Declarative Helpers". From here on.
Thanks in advance for your help.
The standard helpers (such as UrlHelper and HtmlHelper) are not available in a Razor inline #helper. If you need to use it will need to pass the UrlHelper as parameter to your helper:
#helper MenuItem(UrlHelper url, string action, string controller)
{
<a href="#url.Action(action, controller)" id="#controller">
<div class="MenuItem">
<img src="#url.Content("~/XXX.YYY.Web/Content/Images/Icons/Menu/mnu"+controller+".png")" />
<br />
#controller
</div>
</a>
}
and then when calling pass the correct instance:
#LayoutHelpers.MenuItem(Url, "Index", "Home")
#LayoutHelpers.MenuItem(Url, "Index", "Chart")

Categories