oqtane / Oqtane.Survey

An Oqtane module that allows administrators to create user surveys. Oqtane allows developers to create external modules which are rendered by the framework dynamically at run-time.
MIT License
18 stars 7 forks source link

Converting Module to use Migrations has errors and Radzen .css not loading #8

Closed ADefWebserver closed 2 years ago

ADefWebserver commented 2 years ago

Using Oqtane version 3.0.3 and UpgradeToDotNet6 branch of the Survey module.

Build the Survey module to produce Oqtane.Survey.2.0.0.nupkg Oqtane.Survey.2.0.0.nupkg.zip

image

Upload and Install the package.

image

Restart Oqtane. The module will says 1.0.0 with an Upgrade button. If the Upgrade button is not clicked, the database tables will not exist and the mule will not work.

image

If the Upgrade button is pressed, and Oqtane restarted, the Upgrade button will no longer be there, but, the version will still say 1.0.0

image

However, the tables will be created.

image

The module will now work, but, the .css for the Radzen components, will not show up.

image

There are no errors in F12 web tools.

image

We can see that the .css was in the package.

image

sbwalker commented 2 years ago

OK, I will investigate further. Currently I am focused on the auth improvements for 3.1 so I may not get to this until later this week.

sbwalker commented 2 years ago

One question @ADefWebserver - did you have the Survey 1.0 module installed already before trying to install version 2.0?

sbwalker commented 2 years ago

@ADefWebserver The reason why I am asking is because since the module was originally based on SQL scripts, then if you have version 1.0 installed and then try to upgrade to Version 2.0 which is using Migrations, you need to notify EF Core that the 1.0 migrations were already executed. You do this by inserting a record in the Migrations History table. The Blog module had to deal with this scenario as well - so you can see an example here (see the Install method):

https://github.com/oqtane/oqtane.blogs/blob/master/Server/Manager/BlogManager.cs

ADefWebserver commented 2 years ago

One question @ADefWebserver - did you have the Survey 1.0 module installed already before trying to install version 2.0?

@sbwalker - In the example I covered I did not have the Survey 1.0 module installed (I ensured I was starting with a completely fresh Oqtane to ensure everything was clean).

Regarding the upgrade scenario, I created a new issue that I can work on after this initial issue is resolved: https://github.com/oqtane/Oqtane.Survey/issues/9 Thanks!

sbwalker commented 2 years ago

The best way to diagnose Migration issues is to rely on the Log file created in the Oqtane.Server\Content\Log folder. When I run the Survey code in the .NET6 branch it produces these errors in the log:

[2022-03-07 18:33:19+00:00] [Error] [Oqtane.Infrastructure.DatabaseManager] An Error Occurred Installing Survey Version 2.0.0 - Foreign key 'FK_OqtaneSurveyAnswer_SurveyItem' references invalid table 'SurveyItem'. Could not create constraint or index. See previous errors. [2022-03-07 18:33:19+00:00] [Error] [Oqtane.Server.Program.Main] An Error Occurred Installing Survey Version 2.0.0 - Foreign key 'FK_OqtaneSurveyAnswer_SurveyItem' references invalid table 'SurveyItem'. Could not create constraint or index. See previous errors.

So it appears that it is having difficulty creating the SurveyItem table... or there is a problem with the order in which the entities are being created. I will investigate further...

ADefWebserver commented 2 years ago

The best way to diagnose Migration issues is to rely on the Log file created in the Oqtane.Server\Content\Log folder

I added this note to the Oqtane Module Creator tutorial.

sbwalker commented 2 years ago

I have fixed all of the Migration issues... I will send you the ZIP by email. I also was able to identify the issue with CSS loading... which was caused by a change in the core framework. I fixed the issue and will merge it. That being said, I do not believe that the IHostResources approach is the recommended way to register static resources - it was more of a temporary solution while a more robust solution was being developed for Oqtane. The problem with IHostResources is that it registers the assets globally ie. it includes the stylesheet and JavaScript references on every page for every site within your multi-tenant installation - which is generally not a good idea. Declaring the resources locally in the module is much better as it means they are lazy loaded as needed.

ADefWebserver commented 2 years ago

I also was able to identify the issue with CSS loading... which was caused by a change in the core framework. I fixed the issue and will merge it.

@sbwalker - That is wonderful news :) Can you respond to the Discussion issue on how he should proceed?

Declaring the resources locally in the module is much better as it means they are lazy loaded as needed.

@sbwalker - Yes I agree.

ADefWebserver commented 2 years ago

@sbwalker - I incorporated the changes and checked them into this branch. The only issue I have is that when the module is placed on a page, it takes over the .css and it looks like this:

image

ADefWebserver commented 2 years ago

@sbwalker - Also, if I refresh the web browser page it will show an error:

image

An Unexpected Error Has Occurred In Oqtane.Survey, Oqtane.Survey.Client.Oqtane: Could not find 'Radzen.preventArrows' ('Radzen' was undefined). Error: Could not find 'Radzen.preventArrows' ('Radzen' was undefined).

sbwalker commented 2 years ago

@ADefWebserver sorry for taking so long to respond to this... I have been focused on the 3.1 security enhancements. I should have time soon to help with the Survey module.

In regards to the CSS issue, my gut reaction is that Radzen must be using Bootstrap as its CSS framework... and since Oqtane is using Bootstrap as well, it is resulting in strange behavior. When the Radzen CSS was being declared in IHostResources the Bootstrap CSS reference would have been included in the page head BEFORE the Oqtane Bootstrap CSS reference... and because of CSS's cascading nature, the Oqtane Bootstrap CSS reference would have taken precedence resulting in the Oqtane black/blue styling. When the change was made to include the Radzen CSS resources in the module component itself, it resulted in a change to the order of the Bootstrap CSS references which made the Radzen Bootstrap CSS reference take precedence (resulting in the orange color). The "problem" is that the behavior is actually correct - as you do want module-level CSS styles to override page/theme styles. Maybe because there is a shared dependency in this case, there is no need to define the Radzen CSS styles and it can rely on the Oqtane Bootstrap references (this would only be true if Radzen and Oqtane was using the same generation of Bootstrap ie. BS5 ). More investigation will be needed.

EDIT: Here is a useful link: https://forum.radzen.com/t/conflict-with-style-sheets-mixing/3128

ADefWebserver commented 2 years ago

@sbwalker No problem I can wait until you find time. Also, what do you think about:

An Unexpected Error Has Occurred In Oqtane.Survey, Oqtane.Survey.Client.Oqtane: Could not find 'Radzen.preventArrows' ('Radzen' was undefined). Error: Could not find 'Radzen.preventArrows' ('Radzen' was undefined)."

sbwalker commented 2 years ago

I will need to take a closer look at the code changes I sent you

sbwalker commented 2 years ago

@ADefWebserver in my testing if I simply remove the following line from each razor component, it allows the module to behave properly:

    new Resource { ResourceType = ResourceType.Stylesheet, Url = "_content/Radzen.Blazor/css/default.css" },

I am unable to reproduce the JavaScript related error on browser refresh - what brower were you using?

ADefWebserver commented 2 years ago

@sbwalker - I checked in the changes. However, now the Radzen controls don't display properly :(

image

I get the refresh error in both the latest Edge and Chrome web browsers. You may need to create a Survey first to experience the issue.

sbwalker commented 2 years ago

@ADefWebserver Ok... so the base problem is that Radzen's CSS seems to be a combination of the standard Bootstrap styles as well as some custom CSS styles - both included in the same file, which is a really bad approach. If the Bootstrap styles and Radzen custom styles were cleanly separated into their own CSS files then it would be much easier to manage the desired behavior. Perhaps Radzen has made these changes in a more recent version (the link I included in an earlier post seemed to suggest this).

As far as JavaScript is concerned it is usually helpful to know the exact steps to reproduce the problem. I will try to create a survey and see if I can trigger the issue. I prefer Firefox for browsing and web development but I think I may have Edge installed so I could try it as well.

ADefWebserver commented 2 years ago

@sbwalker - Ok I see That Radzen has this that does not include BootStrap: <link rel="stylesheet" href="_content/Radzen.Blazor/css/default-base.css"> I will give this a try and report back

ADefWebserver commented 2 years ago

@sbwalker - I just checked in these changes and it fixes the issue 👍

image

The JavaScript issue is the only one that remains...

Animation

sbwalker commented 2 years ago

That is good news about the CSS. I will investigate the JS issue more in the morning.

ADefWebserver commented 2 years ago

@sbwalker - If I add a HostResources.cs file with this code:

namespace Oqtane.Survey
{
    public class HostResources : IHostResources
    {
        public List<Resource> Resources => new List<Resource>()
        {
            new Resource {
                ResourceType = ResourceType.Script,
                Url = "_content/Radzen.Blazor/Radzen.Blazor.js" }
        };
    }
}

The problem goes away. However, I know you didn't want this method to be used?

sbwalker commented 2 years ago

@ADefWebserver I was able to reproduce the JavaScript issue. And I was also able to get some more insights into what is happening...

The error does not happen consistently on every browser refresh - at least on my machine it happens only about 50% of the time. This means that it is not a logical error - it is dependent upon the state of the environment. Typically this points to a race condition of some sort where one process is executing before another process that it depends on. The browser console provided a helpful clue in the stack trace:

   at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args)
   at Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(IJSRuntime jsRuntime, String identifier, Object[] args)
   at Radzen.Blazor.RadzenDropDown`1.OnAfterRenderAsync(Boolean firstRender)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

So Radzen's dropdown razor component has an OnAfterRenderAsync event which appears to be performing JS Interop and assuming that the JavaScript is already loaded. This would be a valid assumption in most applications which embed the JavaScript references in the page head so they exist during startup. However Oqtane is dynamic and uses lazy loading of resources - so it is possible that the Razen OnAfterRenderAsync sometimes executes BEFORE the Oqtane logic which registers the JavaScript library in the page - ie. a race condition.

So the solution is to ensure the proper ordering of events... and the way to accomplish this in Blazor is to manually direct the event flow. This is not difficult to implement... but it does require some modification to your module components...

In your Index.razor component (and any other components using Radzen) you would need to wrap the UI markup in a flag which indicates whether or not Radzen has been initialized. And you would need to implement the OnAfterRenderAsync event in your module component to ensure that it registers the JavaScript before any Radzen components are executed. The basic code pattern is below:

@if (radzenInitialized)
{
   ... your markup logic
}

@code {
    public override List<Resource> Resources => new List<Resource>()
    {
        new Resource { ResourceType = ResourceType.Stylesheet, Url = "_content/Radzen.Blazor/css/default-base.css" },
        new Resource { ResourceType = ResourceType.Script, Url = "_content/Radzen.Blazor/Radzen.Blazor.js" }
    };

    bool radzenInitialized = false;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender); // allows Oqtane to register the JavaScript library declared in Resources

        if (firstRender && !radzenInitialized)
        {
            radzenInitialized = true;
            StateHasChanged();
        }
    }
}

Obviously this involves a bit more effort to get it working in your module. However the benefit is that your JavaScript library will only be loaded into the browser if a user accesses a page which actually requires it (ie. load on-demand). If you use the IHostResources method that you relied on previously, the JavaScript library is loaded into EVERY page regardless of whether the user will actually need it or not - so it is not an efficient approach (but it is simpler to implement).

ADefWebserver commented 2 years ago

@sbwalker - Ok I will try to make those changes and fix some other bugs I found (you cannot update the name of an existing Survey Item) over the next few weeks. Thanks!

ADefWebserver commented 2 years ago

@sbwalker - I made all the changes and checked them in here:

https://github.com/oqtane/Oqtane.Survey/tree/UpgradeToDotNet6

The only error I have now is when trying to set a selection with the Radzen Multi-select dropdown:

Animation

The error is:

System.TypeInitializationException: The type initializer for 'System.Linq.Dynamic.Core.DynamicQueryableExtensions' threw an exception. ---> System.Exception: Specific method not found: FirstOrDefault ---> System.InvalidOperationException: Sequence contains more than one matching element at System.Linq.ThrowHelper.ThrowMoreThanOneMatchException() at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable1 source, Func2 predicate, Boolean& found) at System.Linq.Enumerable.Single[TSource](IEnumerable1 source, Func2 predicate) at System.Linq.Dynamic.Core.DynamicQueryableExtensions.GetMethod(String name, Int32 parameterCount, Func2 predicate) --- End of inner exception stack trace --- at System.Linq.Dynamic.Core.DynamicQueryableExtensions.GetMethod(String name, Int32 parameterCount, Func2 predicate) at System.Linq.Dynamic.Core.DynamicQueryableExtensions..cctor() --- End of inner exception stack trace --- at System.Linq.Dynamic.Core.DynamicQueryableExtensions.Cast(IQueryable source, Type type) at Radzen.DropDownBase1.SelectItem(Object item, Boolean raiseChange) at Radzen.Blazor.RadzenDropDown1.OnSelectItem(Object item, Boolean isFromKey) at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

I will try this fix next week: https://forum.radzen.com/t/linq-error-with-net-6-0-preview-3-and-dropdown-control/7483/7

ADefWebserver commented 2 years ago

@sbwalker - Yeah upgrading System.Linq.Dynamic.Core to version 1.2.12 fixed the issue. So, I am closing this as fixed with https://github.com/oqtane/Oqtane.Survey/commit/a2f5030f61f3ebfa2bb24b09473edfb8dc8770f1

sbwalker commented 2 years ago

that is good news!

ADefWebserver commented 2 years ago

@sbwalker - I forgot to say: "Thank You! for all your help 🙌🏾"

ADefWebserver commented 2 years ago

@sbwalker - I will spend the next two weeks testing and testing upgrades ect before trying to create a new NuGet package.

shivampayasi01 commented 1 year ago

I trying to use Radzen dropdown in blog module but I'm getting an exception : System.IO.FileNotFoundException: Could not load file or assembly 'System.Linq.Dynamic.Core, Version=1.2.22.0, Culture=neutral, PublicKeyToken=0f07ec44de6ac832'. The system cannot find the file specified. File name: 'System.Linq.Dynamic.Core, Version=1.2.22.0, Culture=neutral, PublicKeyToken=0f07ec44de6ac832' at Radzen.DropDownBase1.OnParametersSet() at Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync() at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync() at Radzen.RadzenComponent.SetParametersAsync(ParameterView parameters) at Radzen.DataBoundFormComponent1.SetParametersAsync(ParameterView parameters) at Radzen.DropDownBase1.SetParametersAsync(ParameterView parameters) at Radzen.Blazor.RadzenDropDown1.SetParametersAsync(ParameterView parameters)