Based on this article, I implemented the following code
https://blog.elmah.io/rendering-dynamic-content-in-blazor-wasm-using-dynamiccomponent/
There is a problem implementing the RenderFragment in the DynamicComponent
Error Image
File ComponentContainer.razor
<div #class="class">
#Container_Content
</div>
[Parameter]
public string class { get; set; };
[Parameter]
public RenderFragment RenderFragment_Content { get; set; }
File appsettings.json
{
"content": [
{
"type": "ComponentContainer",
"parameters": {
"class": "container"
"RenderFragment_Content": {
"type": "ComponentContainer",
"parameters": {
"class": "container"
}
}
}
]
}
File Index.razor
#using Microsoft.AspNetCore.Components;
#using Shahab.Client.Pages.Components
#using Shahab.Client.Pages.Components.Controls
#using Shahab.Client.Pages.Components.Controls.Container
#foreach (var Component in Components)
{
<DynamicComponent Type=#Component.type Parameters=#Component.parameters />
}
#code {
protected List<(Type type, Dictionary<string, object> parameters)> components { get; set; }
//OnInitializedAsync
protected async override Task OnInitializedAsync()
{
components = Configuration
.GetSection("content")
.GetChildren()
.Select(component =>
(
type: Type.GetType($"Shahab.Client.Pages.Components.Controls.Container.{component.GetValue<string>("type")}"),
parameters: component
.GetSection("parameters")
.GetChildren()
.ToDictionary(p => p.Key, p => p.Get<object>())
)
)
.ToList();
}
}
enter image description here
My goal is to put the components together in runtime, even nested.(like page designer sharepoint)
There are two incomplete ways to do this.
1- Using DynamicComponent
2- Using RenderTreeBuilder
Both of these solutions do not work in practice when used in nested(RenderFragment) components.
And a complete way to generate files in run time.(generate .razor)
But the problem with this method is that it is compile in runtime.
(in asp.net web-form and asp.net core/mvc you can use AddRazorRuntimeCompilation method But not work in Blazor)
What is your solution to this challenge ?
You try to deserialize the following to a RenderFragment:
{
"type": "ComponentContainer",
"parameters": {
"class": "container"
}
}
Firstly, you can't make a simple deserialisation from the following to a RenderFragment as RenderFragment is a Delegate type.
Secondly, I don't see how this would match the RenderFragment type if it could be deserialized.
DynamicComponent Parameters accept a dictionary,So you need to create a RenderFragment delegate to add to the dictionary
Related
I'm trying to set up a series of complex app settings in a separate settings.json file - I won't go into detail as to why...
So I have a JSON file which looks like this:
{
"Website": {
"Name": "Website Name",
"api_key": "----------",
"domain": "-----------"
},
"Pages": {
"Index": {
"Name": "Index",
"Widgets": {
"BestSellers": {
"Name": "BestSellers",
"Type": "ProductCollection",
"Data": {
"Limit": "8",
"Sort": {
"SortType": 3
},
"GetFullProducts": true,
"GroupVariations": false
}
}
}
}
}
}
The first section "Website" simply fetches string settings, all working fine.
The section section "Pages" is more complicated. I have classes that look like this:
public class PageSettings
{
public string Name { get; set; }
public Dictionary<String, Widget> Widgets { get; set; }
public class Widget
{
public string Name { get; set; }
public string Type { get; set; }
public Dictionary<String, object> Data { get; set; } // THIS IS THE PROPERTY THIS QUESTION IS ABOUT
}
}
I use this code to deserialise the above:
IConfigurationSection pagessection = root.GetSection("Pages");
if (pagessection.Exists())
{
_Pages = new Dictionary<String, PageSettings>();
pagessection.Bind(_Pages);
}
With the JSON File exactly as above, this will fail. For some reason, the nested Object Sort in the Data property cannot be deserialised as Object:
"Sort": {
"SortType": 3
}
If I take the above nested object out then all the code so far will work. However, there are use cases where I need that nested object.
I have tried using ExpandoObject which is very cool and clever, but because it expects KeyValuePairs, it then only serialises the nested object in Data, ignoring the simple properties Limit, GetFullroduct etc.
So what I need is a form of ExpandoObject which can also be ExpandoString or something?!
Alternatively... I need to be able to get the Data property from the settings.json file in String form and explicitly deserialise it using JsonConvert.Deserialize at the point of use, because at that point I can declare the proper class that it needs to be deserialised to, but i can't seem to find a way to get the IConfigurationSection code to get the value as a string, rather than a JSON object.
I can't find a solution to this, the nested object breaks everything I have tried.
The helpful comment from #Fei Han has helped a little in highlighting the flexibility of the JObject class, so the solution I have come to is this:
The complex class has to be stored as an HTML encoded string in the settings.json file:
"Data": "{"Limit": "8","GetFullProducts":true,"GroupVariations":true, "Sort": {"SortType": 3}}"
it has to be HTMLEncoded because it is the only way I can find to make the ConfigurationBuilder treat it as a string so that I can cast it correctly later.
The corresponding Class for this now has these properties:
public string ModelString { get; set; }
public Newtonsoft.Json.Linq.JObject Model
{
get
{
string s = ModelString.HtmlDecode();
if (s.EmptyStr())
{
return new JObject();
} else {
return JObject.Parse(s);
}
}
}
From this I am able to easily cast my Data to the eventually required class using .ToObject<MyObject>()
For some reason, this works. I am able to deserialise the string to a JObject in this method, but not directly using the Bind command on IConfigurationSection.
If anyone has any tips on why Bind won't do it, that'd be interesting!
I am using Newtonsoft to deserialize data from a file. When I deserialize two different instances from two different sets of data, both instances' property ends up having the same value. I have created a small project to repro the issue. Here are my 2 JSON files
File1.json:
{
"Name": "File1",
"SomeProperty":
{
"Value": 1
}
}
File2.json:
{
"Name": "File2",
"SomeProperty":
{
"Value": 2
}
}
SomeProperty.cs
namespace Json
{
public class SomePropertyDto
{
public static SomePropertyDto Default = new SomePropertyDto
{
Value = 0
};
public int Value { get; set; }
}
}
FileDataDto.cs
namespace Json
{
public class FileDataDto
{
public string Name { get; set; }
public SomePropertyDto SomeProperty
{
get => someProperty;
set => someProperty = value;
}
private SomePropertyDto someProperty = SomePropertyDto.Default;
}
}
Program.cs
using System.IO;
using Newtonsoft.Json;
namespace Json
{
class Program
{
static void Main(string[] args)
{
string json1 = File.ReadAllText("File1.json");
string json2 = File.ReadAllText("File2.json");
FileDataDto fileData1 = JsonConvert.DeserializeObject<FileDataDto>(json1);
FileDataDto fileData2 = JsonConvert.DeserializeObject<FileDataDto>(json2);
}
}
}
After deserializing both instances of FileDataDto, both their SomeProperty values are the same. However if I do not initialise the FileDataDto someProperty field to SomePropertyDto.Default,
private SomePropertyDto someProperty;// = SomePropertyDto.Default;
it works correctly. If I include the initialisation to the default value
private SomePropertyDto someProperty = SomePropertyDto.Default;
after deserializing fileData1, the SomeProperty value equals 1 as expected. However, after deserializing fileData2, both fileData1 and FileData2 instances' SomeProperty value equals 2 which is not what is expected.
According to https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonSerializerSettings.cs#L46, the default object creation setting is "Auto", which means https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/ObjectCreationHandling.cs#L34
Reuse existing objects, create new objects when needed.
So when your Default object is there, someProperty stay this, the same, shared object for all FileDataDto instances.
Provide customized JsonSerializerSettings (with ObjectCreationHandling set to Replace) if you need that Default value.
I have a json configuration file:
{
"chat": {
"host": "http://localhost:4555"
}
}
Also I have created class :
public class ChatConf
{
public String host { get; set; }
}
In my Startup file I'm doing this:
services.AddOptions();
services.Configure<ChatConf>(Configuration.GetSection("chat"));
The question is how can I get value of host in my main Layout?
Thanks.
you need to include Microsoft.Extension.IOption in your controller to work with the IOption collection. Then you can access your class by adding it to the constructor of you controller.
here is a good explanation
I am trying to use ServiceStack Razor in my project. I set up a very simple DTO:
namespace ModelsWeb.Diagnostics
{
[Route("/echo")]
[Route("/echo/{Text}")]
public class Echo
{
public string Text { get; set; }
}
public class EchoResponse
{
public ResponseStatus ResponseStatus { get; set; }
public string Result { get; set; }
}
}
And a service to go with it:
namespace Rest.Services
{
public class EchoService : Service
{
public object Any(Echo request)
{
return new EchoResponse {Result = request.Text};
}
}
}
Note that the DTO and the service are in different namespaces. This is because I'm building two applications at once -- the server and the thick client -- and I put all the DTOs in a separate class library that they both depend on. This way, the client can reference just that class library, and no other server-side code. I am using Razor to provide a Web interface to some of the server functionality.
Anyway, I also wrote a simple view for my Echo service:
#using ServiceStack.Razor
#using ModelsWeb.Diagnostics
#inherits ViewPage<EchoResponse>
#{
ViewBag.Title = "Echo Response";
Layout = "BasePage";
}
<h1>You typed: #Model.Result</h1>
When I type "http://localhost:62061/echo/hello2" into the browser, I get an error on my log:
Cannot implicitly convert type 'ServiceStack.Razor.Compilation.RazorDynamicObject'
to 'ModelsWeb.Diagnostics.EchoResponse'
However, the template still works, and I see the expected result in the browser. What's going on here ? Am I doing anything wrong ? If not, how can I suppress this exception ?
I'd like to create several similar services which can be destinguished and accessed by their names (=keys).
For the service implementation I want to use classes with c'tor dependencies like this:
public interface IXYService
{
string Tag { get; set; }
}
public class _1stXYService : IXYService
{
public _1stXYService(string Tag)
{
this.Tag = Tag;
}
public string Tag { get; set; }
}
What I tried was to use 'AddComponentWithProperties' to have a concrete instance created which is accessible via a given key:
...
IDictionary l_xyServiceInitParameters = new Hashtable { { "Tag", "1" } };
l_container.AddComponentWithProperties
(
"1st XY service",
typeof(IXYService),
typeof(_1stXYService),
l_xyServiceInitParameters
);
l_xyServiceInitParameters["Tag"] = "2";
l_container.AddComponentWithProperties
(
"2nd XY service",
typeof(IXYService),
typeof(_1stXYService),
l_xyServiceInitParameters
);
...
var service = l_container[serviceName] as IXYService;
However, the dependencies were not resolved and hence the services are not available.
Using IWindsorContainer.Resolve(...) to populate the parameters is not desired.
Construction by XML works, but is not in all cases sufficient.
How could I achieve my goals?
If you're looking to define the Tag property at registration-time:
[Test]
public void Named() {
var container = new WindsorContainer();
container.Register(Component.For<IXYService>()
.ImplementedBy<_1stXYService>()
.Parameters(Parameter.ForKey("Tag").Eq("1"))
.Named("1st XY Service"));
container.Register(Component.For<IXYService>()
.ImplementedBy<_1stXYService>()
.Parameters(Parameter.ForKey("Tag").Eq("2"))
.Named("2nd XY Service"));
Assert.AreEqual("2", container.Resolve<IXYService>("2nd XY Service").Tag);
}