I am building a reusable Razor class library for Blazor components.
I have an interface that any Blazor component could implement:
public interface IControllableComponent
{
void SetSomeValue(int someValue);
}
How can I get a List of all components that implement that interface?
public interface IControllableComponentManager
{
void SetSomeValueForAllControllableComponents(int someValue);
}
public class ControllableComponentManager : IControllableComponentManager
{
IList<IControllableComponent> _controllableComponentList;
public ControllableComponentManager()
{
_controllableComponentList = ??? // how to populate this list?
}
public void SetSomeValueForAllControllableComponents(int someValue)
{
foreach (var controllableComponent in _controllableComponentList)
{
controllableComponent.SetSomeValue(someValue);
}
}
}
I want for my reusable Razor class library to be used in the code behind in different projects:
public class MyControllableComponentBase : ComponentBase, IControllableComponent
{
protected int _someValue;
public void SetSomeValue(int someValue)
{
_someValue = someValue;
}
}
Is there a way to populate _controllableComponentList in ControllableComponentManager?
I would like to do this in a clean, proper way, without using reflection. Preferably in the style of ASP.NET Core with dependency injection.
The problem is that Blazor components are instantiated in Razor markup as <Component></Component> so I don't know how to get their references to add them to the List<>.
I guess you can do it as the following:
Define a service that stores a collection of the interface type
Expose a method to add a component, a method to remove a component, an event delegate to notify of addition, removal, etc.
Inject the service into the component you want to add itself to the service, and in the OnInitialized method do something like this:
protected override void OnInitialized()
{
MyComponents.AddComponent(this);
this.MyProperty = "I was born with the sun...";
}
You can get a Blazor component reference using #ref but you can do that only within Razor markup.
To use a reference to a Blazor component outside Razor markup, the component must register itself.
To do that, you have to use a partial class for the component in the code behind:
public partial class ComponentContainer : ComponentBase, IComponentContainer
{
public Type ComponentType { get; protected set; }
public RenderFragment Component { get; set; }
[Parameter]
public string Name { get; set; }
[Inject]
protected IComponentContainerManager ComponentContainerManager { get; set; }
public void SetComponentType(Type componentType)
{
ComponentType = componentType;
StateHasChanged();
}
protected override void OnInitialized()
{
ComponentContainerManager.RegisterComponentContainer(Name, this);
Component = builder =>
{
if (ComponentType != null)
{
builder.OpenComponent(0, ComponentType);
builder.CloseComponent();
}
};
}
}
Use [Inject] attribute to use dependency injection to inject a service where the component can register.
Use the protected override void OnInitialized() of the ComponentBase to register the component in the registration service.
public interface IComponentContainer
{
Type ComponentType { get; }
void SetComponentType(Type componentType);
}
public interface IComponentContainerManager
{
void RegisterComponentContainer(string componentContainerName, IComponentContainer componentContainer);
void SetComponentType(string componentContainerName, Type componentType);
Type GetComponentType(string componentContainerName);
}
public class ComponentContainerManager : IComponentContainerManager
{
readonly IDictionary<string, IComponentContainer> _componentContainerDict = new Dictionary<string, IComponentContainer>();
public void RegisterComponentContainer(string componentContainerName, IComponentContainer componentContainer)
{
_componentContainerDict[componentContainerName] = componentContainer;
}
public void SetComponentType(string componentContainerName, Type componentType)
{
_componentContainerDict[componentContainerName].SetComponentType(componentType);
}
public Type GetComponentType(string componentContainerName)
{
return _componentContainerDict[componentContainerName].ComponentType;
}
}
Now you can use IComponentContainerManager anywhere in your code.
If you want to dynamically create components, then you can use the RenderFragment concept for this. A render fragment is essentially a function that uses a RenderTreeBuilder to create a component. This is what your Razor components are compiled to when you author .razor files.
For example, the following code creates a RenderFragment that just renders a single component:
Type componentType = typeof(MyControllableComponentBase);
RenderFragment fragment = builder =>
{
builder.OpenComponent(0, componentType);
builder.CloseComponent();
};
You could then assign that fragment to a property and render it dynamically within your Razor component:
<div>
#Fragment
</div>
#code {
public RenderFragment Fragment
{ get; set; }
}
This will solve the issue how to create components dynamically when you just know their type. However, it will not solve the problem that you want to call SetSomeValue on the component instances. For that, you will have to understand that you don’t really have control over the component instances: The render tree builder is responsible for creating the components from that virtual markup, so you don’t ever call new ComponentType(). Instead, you rely on the renderer to create the instance for you and then you could take a reference to the used instance and work with that.
You can use the #ref directive to capture references to rendered component instances:
<MyControllableComponent #ref="controllableComponent" />
#code {
private MyControllableComponent controllableComponent;
private void OnSomething()
{
controllableComponent.SetSomeValue(123);
}
}
Related
I have a .NET 5.0 web application that instantiates classes for each of the endpoints. Those classes instantiate child classes. Is there a more elegant or efficient way to access parent instance data from child instances besides the way I'm doing it right now?
As an example:
public class ComponentClass
{
private PageClass _page;
public ComponentClass(PageClass page)
{
_page = page;
}
public void ComponentMethod()
{
// Call the method from the parent instance
page.PageMethod();
}
}
public class PageClass
{
private ComponentClass _component;
public PageClass()
{
_component = new ComponentClass(this);
}
public async Task ProcessRequest(HttpContext context)
{
// Call the component's method
_component.ComponentMethod();
}
public void PageMethod()
{
// Do something here
}
}
Specifically, I'm trying to avoid having to pass this to every ComponentClass instance...
If you want to call a method on the parent, then you have two options. The first is to pass a reference of the parent into the child. There's no way around this, an object has no way to know in which object it is referenced from. In fact, it could be referenced by multiple parent objects.
The better solution is to use events. That way the child never knows anything about the parent(s) and can emit events that any number of components can subscribe to. See here for more details on events. For example, your component could look something like this:
public class Component
{
public event EventHandler Tick;
public void DoSomething()
{
EventHandler handler = Tick;
handler?.Invoke(this, new EventArgs());
}
}
And your PageClass:
public class PageClass
{
public Component _component { get; set; }
public void Init()
{
_component = new Component();
_component.Tick += Component_Tick;
}
public void MakeComponentTick()
{
// This method is just for testing, it's likely this would be triggered by user input
_component.DoSomething();
}
private void Component_Tick(object sender, EventArgs e)
{
Console.WriteLine("Component ticked!");
}
}
I follow official guide to use simple injector to inject object in web form and it works, but now i cant make it works in custon control
this is what i do:
public partial class GestioneAttivita_cnStruttureSocieta : System.Web.UI.UserControl
{
[Import]
public IUnitOfWork iuow { get; set; }
public Domain.Entity.Attivita attivitaGestita {get; set;}
protected void Page_Load(object sender, EventArgs e)
{
using (iuow)
{
attivitaGestita = iuow.Attivita.Read(attivitaGestita.IdAttivita);
}
}
}
but i get null reference exception since iuow is null
i try to edit global.asax to manage UserControl in this way:
private static void RegisterWebPages(Container container)
{
var pageTypes =
from assembly in BuildManager.GetReferencedAssemblies().Cast<Assembly>()
where !assembly.IsDynamic
where !assembly.GlobalAssemblyCache
from type in assembly.GetExportedTypes()
where (type.IsSubclassOf(typeof(Page)) **|| type.IsSubclassOf(typeof(UserControl)))**
where !type.IsAbstract && !type.IsGenericType
select type;
foreach (Type type in pageTypes)
{
var registration = Lifestyle.Transient.CreateRegistration(type, container);
registration.SuppressDiagnosticWarning(
DiagnosticType.DisposableTransientComponent,
"ASP.NET creates and disposes page classes for us.");
container.AddRegistration(type, registration);
}
}
}
class ImportAttributePropertySelectionBehavior : IPropertySelectionBehavior {
public bool SelectProperty(Type serviceType, PropertyInfo propertyInfo) {
// Makes use of the System.ComponentModel.Composition assembly
bool _return = false;
_return = (typeof(Page).IsAssignableFrom(serviceType) &&
propertyInfo.GetCustomAttributes<ImportAttribute>().Any())
**||
(typeof(UserControl).IsAssignableFrom(serviceType) &&
propertyInfo.GetCustomAttributes<ImportAttribute>().Any());**
return _return;
}
}
but i get same error
is this doable?
To be able to get this working, you will have to hook onto the PreLoad event of the page during initialization. During PreLoad you can walk the page's control hierarchy and initialize all controls like you do with the page itself.
There's actually example code in the Simple Injector repository (that never made it to an official package) that shows you how to do this. Take a look here.
I have a class what I would like to close into a component. I try to make it work, based on the following code,
The issue is, that the properties are editable and viewable in the property browser & the Test Event is viewable but it cannot be filled form the property browser, just from the code.
How can I solve this anomaly?
namespace TestComponents
{
public partial class Test: Component
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public SubClass SubClass { get; set; }
public Test()
{
InitializeComponent();
SubClass = new SubClass();
}
}
public delegate void TestEventHandler(Object sender, TestEventArgs e);
public class TestEventArgs: EventArgs
{
public Boolean Test { get; set; }
public TestEventArgs(Boolean ATest): base()
{
Test = ATest;
}
}
[TypeConverterAttribute(typeof(System.ComponentModel.ExpandableObjectConverter))]
public class SubClass
{
public Boolean TestProperty { get; set; }
public event TestEventHandler TestEvent;
protected virtual void OnTestEvent(TestEventArgs e)
{
if (TestEvent != null)
TestEvent(this, e);
}
}
}
Problem solved. If the subclass inherited from Component, the Visual Studio can manage "subclass events" well.
Suspect the property editor doesn't know how it should handle input related to the complex TestEventArgs class, so you can't edit in in the Visual Studio property editor.
You could have a look at writing a Custom UI Type Editor and then specify the custom editor by using the EditorAttribute:
[EditorAttribute(typeof(YourCustomEditor),typeof(System.Drawing.Design.UITypeEditor))]
I've created some custom textboxes which are inherited from textbox.
For the next step I want to register javascript with a wrapper.
Decorator pattern allow me to do if only I can inherit it from textbox and pass custom textbox as a constructor parameter.
Problem is that how can I use constructor when I add a control to aspx page or basically how can I use decorator pattern for asp.net controls.
EDIT:
Simply this is my validation base class (IField is an validation interface. This can be ignored):
public abstract class ValidationBase : TextBox, IField
{
private readonly IField _field;
protected ValidationBase(IField field)
{
_field = field;
}
public int MinLength
{
get { return _field.MinLength; }
set { _field.MinLength = value; }
}
public bool Required
{
get { return _field.Required; }
set { _field.Required = value; }
}
// other porperties etc...
protected override void OnPreRender(EventArgs e)
{
// DO SOME STUFF...
base.OnPreRender(e);
}
}
And this is my concrete class (EmailField is a concrete impl. of IField ignore...):
public class ValidationEmail : ValidationBase
{
public ValidationEmail()
: base(new EmailField(string.Empty))
{
}
}
And finally I want to implement this (I've made up my mind on wordpad this can't be the exact impl.):
public class JsRegisterDecorator : ValidationBase
{
private readonly ValidationBase _validationObj;
//I am not quite sure about the constructor but i can handle
public JsRegisterDecorator(ValidationBase concreteValidationObj)
: base(concreteValidationObj)
{
_validationObj = concreteValidationObj;
}
//Wrap the properties
protected override void OnPreRender(EventArgs e)
{
//Register JS Files...
_validationObj.OnPreRender(e);
}
}
The problem is that How can I use this decorator? Because asp.net construct controls automatically:
<vc:ValidationEmail ID="ValidationEmail1" runat="server"/>
I don't know can I use this (where can I put the constructor parameter?):
<vc:JsRegisterDecorator ID="ValidationEmailWithJs1" runat="server"/>
I don't think Decorator pattern fits well here. In general I saw more applications of Builder and Factory Method for ASP.NET controls.
To partially solve your task you can use ControlBuilder. It will give you ability to change the type of the control from ValidationBase to JsRegisterDecorator or ValidationEmail. You need to decorate ValidationBase class with ControlBuilderAttribute, inherit builder class from ControlBuilder and override Init method.
[ControlBuilder(typeof(ValidationBaseBuilder))]
public abstract class ValidationBase : TextBox, IField { }
public class ValidationBaseBuilder: ControlBuilder
{
public override void Init(TemplateParser parser, ControlBuilder parentBuilder, Type type, string tagName, string id, System.Collections.IDictionary attribs)
{
var newType = typeof(/*here you can put a JsRegisterDecorator type*/);
base.Init(parser, parentBuilder, t, tagName, id, attribs);
}
}
But I'm not sure about such approach. ControlBuilder cannot give you easy control over constructor. Surely you can override ProcessGeneratedCode in ControlBuilder and David Ebbo has a blog post worth reading but it would not be an easy task to rewrite constructor for control and make solution simple.
As alternative that will work I can suggest to add an abstract (or virtual) method like RegisterScripts inside ValidationBase and call it in OnPreRender. Every control will know what scripts it needs and the process of new validator control creation will be clean and simple. If you want to separate knowledge of JS scripts from concrete implementations then approach as seen in ASP.NET DynamicData (read MetaTable) could be used.
Another thing that I can see is that your idea is close enough to DynamicControl and maybe it would be possible to get more ideas from ASP.NET DynamicData like Field Templates and IFielTemplateFactory.
I solve my problem AlexanderManekovskiy's help and also some other questions:
ASP.NET RenderControl or RenderChildren fail
How to add child nodes to custom asp.net user control derived from System.Web.UI.Control
ASP.NET Custom/User Control With Children
And here is the solution:
I've made JsRegistererForValidationBase as a WebControl and implemented INamingContaier.
For the children elements I've created Children property which accepts olny list of Validation Base.
And finally OnInit method, I've registered the js.
Here is the code:
[ParseChildren(true)]
[PersistChildren(true)]
[ToolboxData(#"<{0}:JsRegistererForVB runat=""server""></{0}:JsRegistererForVB>")]
public class JsRegistererForValidationBase : WebControl, INamingContainer
{
private ValidationFieldCollection _children;
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ValidationFieldCollection Children
{
get
{
if (_children == null)
_children = new ValidationFieldCollection();
return _children;
}
}
protected override void CreateChildControls()
{
Controls.Clear();
foreach (var c in _children)
Controls.Add(c);
}
protected override void OnInit(EventArgs e)
{
//DO THE REGISTER STUFF
base.OnInit(e);
}
protected override void Render(HtmlTextWriter writer)
{
RenderChildren(writer);
}
}
public class ValidationFieldCollection : List<ValidationBase> { }
}
And at the aspx side it becomes like this:
<vc:JsRegisterer ID="JsRegisterer1" runat="server">
<Children>
<vc:ValidationEmail ID="ValidationEmail1" runat="server"/>
<vc:ValidationEmail ID="ValidationEmail2" runat="server"/>,
<!--etc-->
</Children>
</vc:JsRegisterer>
For the detailed imlementation I added the code to codeplex
I'm working on a C# project. I'm trying to get rid of a Factory class that has a large switch statement.
I want to configure Autofac to be able to construct a dependency based on a parameter, thereby allowing Autofac to take the place of the Factory.
I've looked at the DelegateFactories page of the Autofac wiki, but I can't figure out how to apply the pattern to an abstract class. Here's some code showing the situation:
public enum WidgetType
{
Sprocket,
Whizbang
}
public class SprocketWidget : Widget
{
}
public class WhizbangWidget : Widget
{
}
public abstract class Widget
{
public delegate Widget Factory(WidgetType widgetType);
}
public class WidgetWrangler
{
public Widget Widget { get; private set; }
public WidgetWrangler(IComponentContext context, WidgetType widgetType)
{
var widgetFactory = context.Resolve<Widget.Factory>();
Widget = widgetFactory(widgetType);
}
}
I'd like it if I were to say new WidgetWrangler(context, WidgetType.Sprocket), its Widget property would be a SpocketWidget.
When I try this, I get errors stating that Widget.Factory is not registered. Does this delegate factory pattern not work with abstract classes, and if so, is there another way to accomplish this?
What you're looking for is the IIndex<,> Relationship Type.
If you register your sub-classes with .Keyed<>(...) you can key a registration to a value (object).
For example:
builder.RegisterType<SprocketWidget>()
.Keyed<Widget>(WidgetType.Sproket)
.InstancePerDependency();
builder.RegisterType<WhizbangWidget>()
.Keyed<Widget>(WidgetType.Whizbang)
.InstancePerDependency();
Then you only require a dependency of IIndex<WidgetType,Widget> to mimic factory behaviour.
public class SomethingThatUsesWidgets
{
private readonly IIndex<WidgetType,Widget> _widgetFactory;
public SomethingThatUsesWidgets(IIndex<WidgetType,Widget> widgetFactory)
{
if (widgetFactory == null) throw ArgumentNullException("widgetFactory");
_widgetFactory = widgetFactory;
}
public void DoSomething()
{
// Simple usage:
Widget widget = widgetFactory[WidgetType.Whizbang];
// Safe Usage:
Widget widget2 = null;
if(widgetFactory.TryGetValue(WidgetType.Sprocket, out widget2))
{
// do stuff
}
}
}
That's using Dependency Injection approach, if you just want to resolve the factory:
var factory = Container.Resolve<IIndex<WidgetType,Widget>>();