I am trying to integrate blazor server into an existing MVC project, and am a little confused as to the requirements for getting my components to call OnAfterRenderAsync().
I have the following simple Index.razor page for testing made up of the following:
#page "/Index"
<h1>Counter</h1>
<p>Current count: #currentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
#code {
private int currentCount = 0;
protected override Task OnAfterRenderAsync(bool firstRender)
{
return base.OnAfterRenderAsync(firstRender);
}
private void IncrementCount()
{
currentCount++;
}
}
If I navigate directly to /Index the OnAfterRenderAsync method is hit just fine. But if I render Index.razor as a component in my cshtml like this:
#(await Html.RenderComponentAsync<Index>(RenderMode.ServerPrerendered ))
Then the OnAfterRenderAsync() is not hit.
I have MapBlazorHub() in my Startup.cs and <script src="~/_framework/blazor.server.js"></script> in my _Host.cshtml.
Have I missed a step somewhere or is this behavior by design in Blazor Server, and if so, is there some workaround?
I need some reusable blazor components to be able to work with JS libraries that were implemented in the MVC project, so I need those components to be able to call OnAfterRenderAsync() without going directly to the page URL
After inspecting the sources in developer tools I saw that the folder for _framework was not present in my component.
Even though I could navigate to https://myurl/_framework/blazor.server.js and saw the file was present in my project, my component didn't have access to it. adding the script tag <script src="~/_framework/blazor.server.js"></script> directly to my cshtml file that was rendering my Blazor component fixed my issue.
Related
I'm looking for a little guidance and suggestions here. My attempts and theories will be at the bottom.
I have a NextJS project from which I want to export the top level component (essentially the entry file) so that I can use it as a preview in my dashboard.
The nextjs project is very simple. For the sake of simplicity, let's imagine that all it renders is a colored <h1>Hello world</h1>. Then in my dashboard, I want to render a cellphone with my NextJS component embedded and then from the dashboard change the color of the text, as a way to preview how it would look like. I hope this makes sense.
I'm lost at how I could export this component from NextJS and import it into my dashboard. The dashboard is rendered in Ruby on Rails. It would be simple enough to just import the repo from git and access the file directly form node_modules, but I'm looking for a solution that doesn't require installing npm on our Rails project.
Paths I have thought about:
1 - Install npm on Rails and just import the source code from NextJS repo and access the file and render with react (Simple, but we're looking for a non-npm solution)
2 - Bundle the component with webpack and load it directly into rails (does this even work?) - I exported the js and all it did was freeze everything :P Still trying this path for now
3 - Using an iframe and just accessing the page (then I can't pass any callbacks into the iframe to change the color directly from the dashboard)
4 - I cannot separate this component from NextJS to use as a library in both repos. The component we are exporting is the "ENTIRE" NextJS app jsx and it wouldn't make sense to separate in a different repo
Does anyone have a suggestion on how I could achieve this?
I think you could use an iframe with the nextjs app url. Then if you want to change the color, simply add the color in query parameter of the iframe and handle it on nextjs app.
Simple example
Rails view (erb)
<iframe src="#{#nextjs_url}?color=#{#color}" />
NextJS
# do something to get the query param of the page and and set to prop of the component
const YourComponent = ({color}) => {
return <h1 style={{color}}>Lorem</h1>;
}
While trying Hoang's solution, I decided to dive deeper into how to communicate with an iframe and the solution actually feels quite good.
You can set up listeners on either side and post messages in between the projects.
So in my dashboard:
function handleEvent(e) {
const data = JSON.parse(e.data)
if (data.type === "card_click") {
//if type is what we want from this event, handle it
}
}
// Setup a listener with a handler
// This will run every time a message is posted from my app
window.addEventListener("message", handleEvent, false)
const postMessage = (color) => {
const event = JSON.stringify({
type: "color_update",
color,
})
// Find the iframe and post a message to it
// This will be picked up by the listener on the other side
document.getElementById("my-iframe-id").contentWindow.postMessage(event, "*")
}
And on my app:
function handleEvent(e) {
const data = JSON.parse(e.data)
if (data.type === "color_update") {
// Do whatever is necessary with the data
}
}
// Setup listener
// This will fire with every message posted from my dashboard
window.addEventListener("message", handleEvent, false)
const handleCardClick = (cardIndex) => {
const event = JSON.stringify({
type: "card_click",
cardIndex,
})
// post message to parent, that will be picked up by listener
// on the other side
window.parent.postMessage(event, "*")
}
It feels pretty straight forward to communicate with an iframe with this solution.
I have Blazor Webassembly ASP.NET Core hosted and I installed Swashbuckle.AspNetCore to display endpoints that my Blazor app has (/swagger endpoint).
My Startup.Configure looks like this (only swagger part):
app.UseSwagger();
app.UseSwaggerUI(c =>
{
foreach (var description in provider.ApiVersionDescriptions)
{
c.SwaggerEndpoint($"{description.GroupName}/swagger.json", $"v{description.GroupName.ToUpperInvariant()}");
}
c.InjectStylesheet("/css/swaggerDark.css");
});
As you can see, I inject custom .css file which works.
In my Blazor app, I inject swagger so my page looks like this (.razor page):
<iframe src="swagger"/>
Again, it works correctly, swagger documentation is displayed and it has dark theme.
I have noticed (to no suprise) that this iframe has a link to this .css file:
<link href="/css/swaggerDark.css" rel="stylesheet" media="screen" type="text/css">
Removing this link brings the default swagger look (light theme).
The user of my app can choose which theme he wants (light/dark) of the whole application. My question is, how do I dynamically inject/remove (or maybe enable/disable) this .css file so depending on which app theme the user chooses, the swagger will either display default (light) or dark theme (using that .css file)?
I couldn't find any relevant info on this issue so I decided to create this question. I appreciate any help. Thank you.
Ok, I figured it out. The answer is: use JsInterop.
My .razor page looks like this at the moment:
#page "/something"
#inject IJSRuntime JS //needed to call InvokeVoidAsync
//I made my own ThemeManager to control the Blazor app theme
#if (ThemeManager.IsDefaultTheme)
{
<iframe id="myiframe" src="swagger" #onload="() => ToggleSwaggerTheme(true)" />
}
else
{
<iframe id="myiframe" src="swagger" #onload="() => ToggleSwaggerTheme(false)" />
}
#code {
private async Task ToggleSwaggerTheme(bool isLight) => await JS.InvokeVoidAsync("toggleSwaggerTheme", isLight);
}
I made it so the iframe toggles depending on the app theme. The ToggleSwaggerTheme function is self-explanatory - I'm calling my JS function to toggle the theme of swagger.
In index.html I added a script to load my helperFunctions.js (in body) in which toggleSwaggerTheme function can be found:
<script src="/js/helperFunctions.js"></script>
I added helperFunctions.js in my wwwroot:
wwwroot -> js -> helperFunctions.js
My helperFunctions.js looks like this:
function toggleSwaggerTheme(isLight) {
let myiframe = document.getElementById('myiframe');
let styleSheets = myiframe.contentWindow.document.styleSheets;
for (let i = 0; i < styleSheets.length; i++) {
if (styleSheets[i].href == null) {
continue;
}
if (styleSheets[i].href.includes("/css/swaggerDark.css")) {
styleSheets[i].disabled = isLight;
break;
}
}
}
The above function allowed me to toggle the swagger theme. Please note, I don't know if this is the best solution that can be created. Also, I'm not sure how it's going to behave in the production environment. I will update this answer if there are any problems in the production. I hope it can help somebody.
UPDATE AFTER GOING LIVE:
I've had some issues with displaying dark theme after going live. But the following seems to fix the issue:
In Program.cs (Server side), in CreateHostBuilder method I added (not sure if it's needed):
webBuilder.UseStaticWebAssets();
So in my case, it looks like this:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStaticWebAssets();
webBuilder.UseStartup<Startup>();
});
I also changed my swaggerDark.css file property: the Copy to Output Directory to Copy always.
You do this by right-clicking the file in Solution Explorer -> Properties (at the end).
After doing these new steps, It seems to be working fine. I hope it can help somebody.
I load all my external script files async, when evrything is loaded, I have a callback that calls the remaining function.
<script>
function executeLayoutInitialization()
{
// Execute basic functions...
#RenderSection("deferredscripts", false)
}
</script>
On some of my views (.cshtml), I load deferred scripts like this:
#section deferredscripts
{
console.info("view loaded");
}
All of this works great, however, I lose JavaScript intellisense in doing so. To combat, I added script tag around the section. It looks like this:
<script>
#section deferredscripts
{
console.info("view loaded");
}
</script>
This now gives me intellisense. However, I don't like that every view now has an empty script tag created. Also, ReSharper 2017.3.2 thinks it's not JS, so it throws error like "; expected" on something like '#Url.Action("Load", "Action"){resharper_errorHere}'
So, my question is, is there a way I can have JS intellisense on my razor view without specifying the script tag? Or, is there a better way to solve this problem? Thank you.
I'm using Handlebars.js, and currently all my templates live inside script tags which live inside .html files housing dozens of other templates, also inside script tags.
<script type="text/template" id="template-1">
<div>{{variable}}</div>
</script>
<script type="text/template" id="template-2">
<div>{{variable}}</div>
</script>
<script type="text/template" id="template-3">
<div>{{variable}}</div>
</script>
...
Then I include this file on the server-side as a partial.
This has the following disadvantages:
A bunch of templates are crammed into HTML files.
Finding a given template is tedious.
I'm looking for a better way to organize my templates. I'd like each each template to live in its own file. For example:
/public/views/my_controller/my_action/some_template.html
/public/views/my_controller/my_action/some_other_template.html
/public/views/my_controller/my_other_action/another_template.html
/public/views/my_controller/my_other_action/yet_another_template.html
/public/views/shared/my_shared_template.html
Then at the top of my view, in the backend code, I can include these templates when the page loads, like this:
SomeTemplateLibrary.require(
"/public/views/my_controller/my_action/*",
"/public/views/shared/my_shared_template.html"
)
This would include all templates in /public/views/my_controller/my_action/ and also include /public/views/shared/my_shared_template.html.
My question: Are there any libraries out there that provide this or similar functionality? Or, does anyone have any alternative organizational suggestions?
RequireJS is really good library for AMD style dependency management. You can actually use the 'text' plugin of requireJS to load the template file in to your UI component. Once the template is attached to the DOM, you may use any MVVM, MVC library for bindings OR just use jQuery events for your logic.
I'm the author of BoilerplateJS. BoilerplateJS reference architecture uses requireJS for dependency management. It also provides a reference implementations to show how a self contained UI Components should be created. Self contained in the sense to handle its own view template, code behind, css, localization files, etc.
There is some more information available on the boilerplateJS homepage, under "UI components".
http://boilerplatejs.org/
I ended up using RequireJS, which pretty much let me do this. See http://aaronhardy.com/javascript/javascript-architecture-requirejs-dependency-management/.
I use a template loader that loads the template using ajax the first time it is needed, and caches it locally for future requests. I also use a debug variable to make sure the template is not cached when I am in development:
var template_loader = {
templates_cache : {},
load_template : function load_template (params, callback) {
var template;
if (this.templates_cache[params.url]){
callback(this.templates_cache[params.url]);
}
else{
if (debug){
params.url = params.url + '?t=' + new Date().getTime(), //add timestamp for dev (avoid caching)
console.log('avoid caching url in template loader...');
}
$.ajax({
url: params.url,
success: function(data) {
template = Handlebars.compile(data);
if (params.cache){
this.templates_cache[params.url] = template;
}
callback(template);
}
});
}
}
};
The template is loaded like this:
template_loader.load_template({url: '/templates/mytemplate.handlebars'}, function (template){
var template_data = {}; //get your data
$('#holder').html(template(template_data)); //render
})
there's this handy little jquery plugin I wrote for exactly this purpose.
https://github.com/cultofmetatron/handlebar-helper
Well, I think the ASP.NET MVC team released a pretty significant bug in the developer preview for asp.net mvc 4, or I'm doing something stupid... Here's the issue and steps to reproduce.
Create a new MVC 4 mobile application
create a new section in the layout (ex. #RenderSection("head",false))
in the controller action just throw a Message into the ViewBag
then in a view that uses the main layout, add the following code below.
#section head {
$(function() {
var newVariableName = "#(ViewBag.Message)";
});
}
You'll notice that the razor parser actually thinks that the section has been completed for the jquery on dom loaded ending brace instead of the section's ending brace. I tried the exact same code in an asp.net MVC 3 application and it worked with no issues.
Has anyone else come across this bug in the ASP.NET MVC 4 Developer Preview?
A quick hack to solving this issue is to use < text > blocks < /text > around the java script. Here's what it might look like until the ASP.NET MVC team resolves this bug.
#{
<text>
$(function()
{
var newVariableName = "#(ViewBag.Message)";
});
</text>
}
As mentioned above, try this in your cshtml file...
#section head {
#{
function JSMeth1()
{
// doing your stuff, razor parser wont suck
}
}}