Is there a template/component solution in ASP.NET Core Razor, that supports inner Razor markup?
Here's a use case:
1. Say I have some repetitive markup for example "a div with nice borders, shadows and two buttons at the bottom"
2. Obviously this markup has a common "header" and a "footer" in the HTML code
3. I need to pass arbitrary Razor markup to insert between header and footer. Not just a model object - but some actual markup that will be rendered between header and footer. I can't use foreach because this markup is different every time - it can be text-content, a form, an image, or some complicated Razor-rendered stuff.
Basically I'm looking for a "Surround this Razor with more Razor" templating solution
Something like:
#{
//this function renders my beautiful box
Func<dynamic, IHtmlContent> myFunction = #<div class="relative flex flex-col rounded-2xl border border-gray-200 bg-white p-8 shadow-sm">
#item
</div>;
}
<!-- and then I call it passing some Razor as input -->
#myFunction(
<ul>
<li>#SomeRazorMethod()</li>
</ul>
);
Something like a Layout - but the one I can use multiple times on the same page.
Is there anything like that? This is a pretty common componentizing tool - "wrap my markup with other markup" - that is present in other templating engines (React, Vue, etc), but apparently not in Razor.
Just to be clear: I'm looking for a Razor-based solution, not a C#-based one. So that my header-footer markup stays in markup files (.cshtml), not in C# files that will have hard-coded HTML magic strings.
Based on your example, this might help.
#functions {
public static IHtmlContent MyBox(dynamic item, Func<dynamic, IHtmlContent> template)
{
var html = new HtmlContentBuilder();
html.AppendHtml("<div class='bestcss'>");
html.AppendHtml(template(item));
html.AppendHtml("</div>");
return html;
}
}
#MyBox(null, #<div class='innercss'>#(10 == 12 ? "MyTest Equals" : "No Equal") hello</div>)
And if you like to pass modeldata, it will be:
#MyBox(customerdata, #<div class='innercss'>#(10 == 12 ? "MyTest Equals" : "No Equal") hello #item.FirstName</div>)
I have used some arbitrary if condition for testing.
You can use Partial Pages or Views which are Razor files containing snippets of HTML and server-side code to be included in any number of pages or layouts.
Just like standard Razor pages, partial pages support the #model
directive specifying the type for the partial's data model. All of the
rendering methods have overloaded versions that take a model to be
consumed in the partial.
#Shah's answer got me one step closer to a solution, however, it seems like the the actual question I'm trying to solve is "can I pass Razor markup as an input parameter".
Turns out you can, you just have to put # in front of it:
#{
void MyFunc(Func<object, IHtmlContent> template)
{
<div>#template(null)</div>
}
}
#{ MyFunc(#<div>The ID is: #Model.Id</div>); }
<!-- mind the '#' before the 'div' -->
Related
Good evening every one .
i would like to add an image near a navbar , but i'm not able to imitate the code propose by bootstrap , because ASP.net use helpers of razor while in bootstrap web site there use diretly an html code.
please how could i adapt this in ASP.net
You should try following code instead of the HTML of bootstrap,
where imagepath is your image path.
Razor Syntax Rules for C#:
Razor code blocks are enclosed in #{ ... }
Inline expressions (variables and functions) start with #
C# code is case sensitive
Variables are declared with the var keyword
Code statements end with semicolon
Strings are enclosed with quotation marks
C# files have the extension .cshtml
#model YourProject.Models.ImageModel
#{
imagePath= "xyz/xyz.jpg"
}
#if(imagePath != ""){
<img src="#imagePath" alt="Image Name" width="200px" />
}
If you take image path from model then you try following code:
#model YourProject.Models.ImageModel
#if(Model.Photo != null && Model.Photo != ""){
<img src="#Url.Content(#Model.Photo)" alt="#Model.AlternateText" />
}
If you don't understand then please refer following link:
https://learn.microsoft.com/en-us/aspnet/web-pages/overview/ui-layouts-and-themes/9-working-with-images
I have written a tag helper that I can use as follows...
<mytaghelper attr1="jim"></mytaghelper>
I would like to be able to shorten this to just...
<mytaghelper attr1="jim">
...or at least...
<mytaghelper attr1="jim"/>
However, I can't get this to work. Here is some sample code for the Process method...
public override void Process(TagHelperContext context, TagHelperOutput output) {
output.TagName = "div";
output.PreContent.SetHtmlContent("<div>");
output.Content.SetHtmlContent("OUTPUT HTML GOES HERE");
output.PostContent.SetHtmlContent("</div>");
output.Attributes.Clear();
}
I have tried adding a TagStructure setting to the HtmlTargetElement attribute on the class...
[HtmlTargetElement("mytaghelper", TagStructure = TagStructure.WithoutEndTag)]
...but it doesn't seem to make any difference. <mytaghelper attr1="jim"/> generates <div /> and <mytaghelper attr1="jim"></mytaghelper> generates <div></mytaghelper>.
If I set the TagStructure to NormalOrSelfClosing then included a closing tag works, but <mytaghelper attr1="jim"/> gives an empty <div />
Anyone able to explain what I need to do?
TagStructure.WithoutEndTag is able to write the tag with only a start tag or self-closing, but the output would be <div > or <div/> . Self-closing anchor tags are not valid HTML, so you wouldn't want to create one, but you might want to create a tag helper that's self-closing. Tag helpers set the type of the TagMode property after reading a tag. Add the below code line Inside the process method:
output.TagMode = TagMode.StartTagAndEndTag;
Take a moment to read Author Tag Helpers in ASP.NET Core which covers this perfectly .
The correct syntax is:
[HtmlTargetElement("mytaghelper", TagStructure = TagStructure.WithoutEndTag)]
Which should be applied to the taghelper class, not the Process method. You may already be doing that, but it wasn't clear in your question. I believe you still must use the self-closing tag syntax (/>) for it work, though.
I have a partial view (_FormCustomer) that displays a form for creating a customer. I also have a View Component (Countrylist) that generates a options list of countries. Now I want to show the country list in my form. This is what I do:
Index.cshtml
<partial name="_FormCustomer" for="#Model._Customer" />
_FormCustomer.cshtml
<select asp-for="#Model.Land" class="form-control">
#await Component.InvokeAsync("Countrylist");
</select>
CountrylistViewComponent.cs
public async Task<IViewComponentResult> InvokeAsync()
{
return View(await _countryRepository.GetCountriesAsync());
}
(The function GetCountriesAsync() returns a list of countries; this works fine.)
Pages/Componenst/Countrylist/default.cshtml
#model List<Country>
#foreach (Country country in Model)
{
<option value="#country.code">#country.name</option>
}
Unfortunately, select-box stays empty when I call the partial. When I call #await Component.InvokeAsync("Countrylist"); directly from Index.cshtml, however, it works fine.
So it looks like you cannot use a View Component inside a Partial View. Is this conclusion right? Or am I doing something wrong?
Thanks Phantom2018, found the problem after your post.
#0: I'm using Razor pages
#1: this had no effect
#2: this was a typo in my question, not in my code
#3: the debugger shows me that the vie component gets called, so
My actual code is a little different, I want to pre select a country if it's available:
<select asp-for="#Model.Country" class="form-control">
#if (Model == null)
{
await Component.InvokeAsync("Countrylist");
}
else
{
await Component.InvokeAsync("Countrylist", Model.Country);
}
</select>
And after some testing, I found the solution:
<select asp-for="#Model.Country" class="form-control">
#if (Model == null)
{
#await Component.InvokeAsync("Countrylist");
}
else
{
#await Component.InvokeAsync("Countrylist", Model.Country);
}
</select>
Don't know why, but I had to use #'s before the awaits.
I have now tested this scenario and can confirm that the data loads fine - both, when the view component is directly included on the page or when it is included in a partial View. (I have tested this on Razor pages - but it is likely to work the same when using MVC. You have not mentioned if you are using MVC or Razor pages.)
A couple of things you can try to see if the loading works fine:
1) From all "Select"s and "Partials" remove the "for*" attributes. That way you can first check if the data loads & then you can worry about binding to the selected item. (Also, in your provided code, you have omitted the model variables - so it is not possible to comment on them.)
2) Remove the last ";" in your _FormCustomer.cshtml
<select asp-for="#Model.Land" class="form-control">
#await Component.InvokeAsync("Countrylist")
</select>
Note that I have removed the trailing ";" in the await statement. I noticed that the ";" was added as another "option" in the select !
3) I also noticed that even minor syntax errors (not picked up by Intellisense) can cause the select to not load. Debug to see if your InvokeAsync is actually being called - in a scenario where there was a minor syntax error, the InvokeAsync was not even being called.
Also keep in mind that:
"When a partial view is instantiated, it receives a copy of the
parent's ViewData dictionary. Updates made to the data within the
partial view aren't persisted to the parent view. ViewData changes in
a partial view are lost when the partial view returns."
I'm trying to make a blog using Vue as laid out in the excellent demo here. I'd like to include some mathematical formulas and equations in my blog, so I thought I'd try to use vue-katex. vue-katex formats my mathematical notation perfectly when I put all my KaTeX HTML directly into my Vue templates, but to create a useable blog I need to keep my content separate from my templates (as shown in the demo).
I can't get vue-katex to format HTML content in the static folder. That's what I'd like help with.
Setup
I cloned the github repo for the demo.
I added vue-katex to package.json:
"vue-katex": "^0.1.2",
I added the KaTeX CSS to index.html:
<!-- KaTeX styles -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-alpha2/katex.min.css"
integrity="sha384-exe4Ak6B0EoJI0ogGxjJ8rn+RN3ftPnEQrGwX59KTCl5ybGzvHGKjhPKk/KC3abb"
crossorigin="anonymous"
>
I added the import statement to src/App.vue:
import Vue from 'vue'
import VueKatex from 'vue-katex'
Vue.use(VueKatex)
and I added a simple line of HTML with KaTeX to the BlogPost template:
<p>Here's an equation in the actual Vue template: <div class="equation" v-katex="'X \\sim N(\\mu, \\sigma^2)'"></div></p>
As I said, this works - I see formatted mathematical notation in my blog post (URL http://localhost:8080/read/neque-libero-convallis-eget):
However, I need different equations for every blog post, of course.
So I tried adding KaTeX HTML to the "content" field in the JSON for the first blog post: static/api/post/neque-libero-convallis-eget.json. I changed the "content" line to:
"content": "Here's an equation in the static folder: <div class=\"equation\" v-katex=\"'X \\sim N(\\mu, \\sigma^2)'\"></div>",
This content appears on the page, but the equation doesn't render. I see this: (the text appears but no equation is shown)
When I use Developer Tools to inspect the HTML on the page, I see this:
You can see that vue-katex has been applied to the equation I put in the template directly: it has parsed the HTML I typed into lots of spans with all the mathematical symbols, which are showing perfectly.
However the KaTeX HTML I've added to the "content" in the static folder has simply been placed on the page exactly as I typed it, and is therefore not showing up as an equation on the page. I really need to keep my blog post content in this static folder - I don't want to have to create a different .vue file for each blog post, that defeats the point!
My question is: is there a way to manually "apply" vue-katex to the HTML I place in the static folder, when it loads? Perhaps there is something I can add to the plugins/resource/index.js file, since this contains the function that loads the data from the static folder?
Many thanks in advance for any help.
*Disclaimer: I'm definitely no expert / authority on what I'm about to explain!
One thing to remember is that Vue reads the templates you write, and then replaces them as reactive components. This means that although you often write Vue attributes like v-for, v-html or in this case v-katex these attributes are only useful up until the app or component is mounted.
With this in mind, if you have a Vue app that ajax loads some html, its not going to be able to rerender itself with those Vue bindings in place.
I have somewhat ignored your current set up and set about solving the issue in another way.
Step 1: Reformat your data from the server side
I've put the posts into an array, and each post contains the template (just a string of html) and the equations separately as an array. I've used [e1] in the post as a placeholder for where the katex will go.
var postsFromServer = [{
content : `<div>
<h2>Crazy equation</h2>
<p>Look here!</p>
[e1]
</div>`,
equations : [
{
key : 'e1',
value : "c = \\pm\\sqrt{a^2 + b^2}"
}
]
}];
Step 2: When the post is rendered, do some work on it
Rather than just use v-html="post.content", I've wrapped the html output in a method
<div id="app">
<div v-for="post in posts" v-html="parsePostContent(post)">
</div>
</div>
Step 3: Create a method that renders all the katex, and then replaces the placeholders in the post
methods : {
parsePostContent(post){
// Loop through every equation that we have in our post from the server
for(var e = 0; e < post.equations.length; e++){
// Get the raw katex text
var equation = post.equations[e].value;
// Get the placeholder i.e. e1
var position = post.equations[e].key;
// Replace [e1] in the post content with the rendered katex
post.content = post.content.replace("[" + position + "]", katex.renderToString(equation));
}
// Return
return post.content;
}
}
Here is the whole set up, which renders Katex:
https://codepen.io/EightArmsHQ/pen/qxzEQP?editors=1010
I have created a test form and displayed using sitefinity's default mvc form widget and when submitting the form it displays thank you message(directly as text) without any html. I want to add some html with it so that i can style it as per my liking.
Change the form on the page to use UseAjaxSubmit from the widget designer model. After this go to "\ResourcePackages\Bootstrap\MVC\Views\Form\Index.cshtml" and find this piece of code.
if (Model.UseAjaxSubmit)
{
<h3 data-sf-role="success-message" style="display: none;">
#Model.SuccessMessage
<div>my customized message</div>
</h3>
You can change the rendering directly in the default template but I would recommend you to create a new template for the widget and keep the default one.
The Sitefinity Knowledgebase contains workarounds to override this when using AjaxSubmit or not.
If using AjaxSubmit is not enabled:
Create a new file under the Form folder (again, ResourcePackages > Bootstrap > MVC > Views > Form) with the following name: "Form.SubmitResultView.cshtml"
Use the following to display the styled text:
#Html.Raw(HttpUtility.HtmlDecode(ViewBag.SubmitMessage))
I ended up having to add my Form.SubmitResultView.cshtml under Mvc > Views > Forms and used the following markup as I needed a consistent wrapper div:
<div class="some-class">
#ViewBag.SubmitMessage
</div>