Passing parent component to all child components in blazor via rendertree - forms

I created an custom form component in blazor and inherited from the default EditForm component to add some functionality.
public class CustomForm : EditForm
I want to pass the instance of the form component to all its children, so a child can retrieve it via a cascading parameter like so
[CascadingParameter]
public CustomForm Form { get; set; }
I took over the BuildRenderTree Method of the default Editform
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
Debug.Assert(EditContext != null);
// If _editContext changes, tear down and recreate all descendants.
// This is so we can safely use the IsFixed optimization on CascadingValue,
// optimizing for the common case where _editContext never changes.
builder.OpenRegion(EditContext.GetHashCode());
builder.OpenElement(0, "form");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "onsubmit", _handleSubmitDelegate);
builder.OpenComponent<CascadingValue<EditContext>>(3);
builder.AddAttribute(4, "IsFixed", true);
builder.AddAttribute(5, "Value", EditContext);
builder.AddAttribute(6, "ChildContent", ChildContent?.Invoke(EditContext));
builder.CloseComponent();
builder.CloseElement();
builder.CloseRegion();
}
But i do not know how to manipulate this code to achieve my goal.

It's possible to pass any object as cascading value so you can override the BuildRenderTree like this and the CustomForm instance will be passed to the child components. Explicit cast to RenderFragment is needed to create delegate of this type, otherwise CascadingValue<>.SetParametersAsync will throw an invalid cast exception.
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;
namespace BlazorTest
{
public class CustomForm : EditForm
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenRegion(0);
builder.OpenComponent<CascadingValue<CustomForm>>(1);
builder.AddAttribute(2, "Value", this);
builder.AddAttribute(3, "ChildContent", (RenderFragment)base.BuildRenderTree);
builder.CloseComponent();
builder.CloseRegion();
}
}
}
I tested this with these components:
CustomFormComponent.razor
<h3>CustomFormComponent</h3>
#code {
private CustomForm _form;
[CascadingParameter]
public CustomForm Form
{
get => _form;
set
{
_form = value;
Console.WriteLine("CustomForm set in CustomFormInput");
}
}
}
TestPage.razor
#page "/test"
<CustomForm Model=model>
<CustomFormComponent />
</CustomForm>
#code {
private AModel model = new();
private class AModel
{
public string Name { get; set; }
}
}
I just want to add that I'm not an expert in this and I'm not sure if you should do it this way. Maybe it would be better to use EditForm as a child component of the CustomForm because you don't have to worry about BuildRenderTree method when using razor markup.
<CascadingValue Value=this>
<EditForm Model=model>
#ChildContent
</EditForm
</CascadingValue>

Related

ViewModels references in ShellViewModel Caliburn.Micro

In this thread : Can anybody provide any simple working example of the Conductor<T>.Collection.AllActive usage? I've had part of an answer but I'm still a but confused.
I would simply like to reference all my view models into my ShellViewModel to be able to open/close ContentControls, but without injecting all of them in the constructor.
In the answer, it is suggested to inject an interface in the constructor of the ShellViewModel. If I do that, do I have to inject all my ViewModels in a class that implements that interface?
public MyViewModel(IMagicViewModelFactory factory)
{
FirstSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
SecondSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
ThirdSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
Items.Add(FirstSubViewModel);
Items.Add(SecondSubViewModel);
Items.Add(ThirdSubViewModel);
}
Also, I would like to avoid going through IoC.Get<> to get the instances of my view Models, I think it violates the principles of IoC if I am not mistaken.
In a few other examples, they create new viewModels when needed, but what's the point of using IoC in that case, especially when I need services injected inside those new ViewModels?
In my Shell view, I have a layout with 3 different areas, bound to my shell view model by :
<ContentControl x:Name="Header"
Grid.ColumnSpan="3"/>
<ContentControl x:Name="Menu"
Grid.Row="1"/>
<ContentControl x:Name="Main"
Grid.ColumnSpan="3"/>
In my ShellViewModel extending Conductor.Collection.AllActive, I reference the 3 areas like this:
public Screen Menu { get; private set; }
public Screen Header { get; private set; }
public Screen Main { get; private set; }
I would like to be able to change them like so:
Menu = Items.FirstOrDefault(x => x.DisplayName == "Menu");
Header = Items.FirstOrDefault(x => x.DisplayName == "Header");
Main = Items.FirstOrDefault(x => x.DisplayName == "Landing");
All my ViewModels have a DisplayName set in their constructor.
I have tried this but GetChildren() is empty
foreach (var screen in GetChildren())
{
Items.Add(screen);
}
Am I missing something obvious?
Thanks in Advance!
Finally, I managed to find an answer myself. It's all in the AppBootstrapper!
I ended up creating a ViewModelBase for my Screens so that they could all have an IShell property (so that the ViewModels could trigger a navigation in the ShellViewModel) like so:
public class ViewModelBase : Screen
{
private IShell _shell;
public IShell Shell
{
get { return _shell; }
set
{
_shell = value;
NotifyOfPropertyChange(() => Shell);
}
}
}
then in the AppBoostrapper registered them like this :
container.Singleton<ViewModelBase, SomeViewModel>();
container.Singleton<ViewModelBase, AnotherViewModel>();
container.Singleton<ViewModelBase, YetAnotherViewModel>();
then created an IEnumerable to pass as param to my ShellViewModel ctor:
IEnumerable<ViewModelBase> listScreens = GetAllScreenInstances();
container.Instance<IShell>(new ShellViewModel(listScreens));
then passing the IShell to each ViewModels
foreach (ViewModelBase screen in listScreens)
{
screen.Shell = GetShellViewModelInstance();
}
for the sake of completeness, here are my GetAllScreenInstances() and GetShellViewModelInstance() :
protected IEnumerable<ViewModelBase> GetAllScreenInstances()
{
return container.GetAllInstances<ViewModelBase>();
}
protected IShell GetShellViewModelInstance()
{
var instance = container.GetInstance<IShell>();
if (instance != null)
return instance;
throw new InvalidOperationException("Could not locate any instances.");
}
Here's what my ShellViewModel ctor looks like:
public ShellViewModel(IEnumerable<ViewModelBase> screens )
{
Items.AddRange(screens);
}
Hope this can help someone in the future!

Using Autofac for WebForms and n-tier architecture

I am introducing unit testing to an existing webforms application. I am using Moq and Autofac. I'm trying to keep code changes to a minimum, but need to implement the unit testing.
So I have a presentation assembly, a business layer, and a data access layer. My business layer looks something like this:
public class EmployeeBL
{
public Employee SelectById(int id)
{
return new EmployeeDA().SelectById(id);
}
}
My Data access looks something like this:
public class EmployeeDA
{
// unitOfWork defined in the constructor
public Employee SelectById(int id)
{
return unitOfWork.Employees.Where(e => e.id == id);
}
}
Autofac's webforms assembly allows you to declare a public property and specify web.config settings to automatically inject a dependency via HttpModule. To test the EmployeeBL, I made the changes to web.config and declared EmployeeDA as a property:
// Modified for testing
[InjectProperties]
public class EmployeeBL
{
// public property to allow Autofac property injection
public IEmployeeDA EmployeeDA { get; set; }
public Employee SelectById(int id)
{
return EmployeeDA.SelectById(id);
}
}
This works for running the webforms application, but the problem is I need to unit test the business layer independently of a webforms instance. I would like to keep the [InjectProperties] attribute on my EmployeeBL, but I can't tell by the documentation how I can get the EmployeeDA to have its properties injected.
public class EmployeeBLTest
{
private static IContainer { get; set; }
[TestInitialize]
public void Initialize()
{
var builder = new ContainerBuilder();
// Use my fake employeeDA for testing
builder.RegisterType<FakeEmployeeDA>().As<IEmployeeDA>();
// Something magical happens
}
[TestMethod]
public void SelectByIdTest(int id) { /* testing stuff */ }
}
What about:
builder.RegisterType<EmployeeDA>().AsSelf().PropertiesAutowired();
see here:
http://code.google.com/p/autofac/wiki/PropertyInjection

How can I marry AutoCompleteBox.PopulateComplete method with the MVVM paradigm?

Here is the setup:
I have an autocompletebox that is being populated by the viewmodel which gets data from a WCF service. So it's quite straightforward and simple so far.
Now, I am trying to follow the principles of MVVM by which the viewmodel doesn't know anything about the view itself. Which is good, because I bound the Populating event of the autocomplete box to a method of my viewmodel via triggers and commands.
So the view model is working on fetching the data, while the view is waiting. No problems yet.
Now, the view model got the data, and I passed the collection of results to a property bound to the ItemSource property of the control. Nothing happens on the screen.
I go to MSDN and to find the officially approved way on how this situation is supposed to be handled (http://msdn.microsoft.com/en-us/library/system.windows.controls.autocompletebox.populating(v=vs.95).aspx):
Set the MinimumPrefixLength and MinimumPopulateDelay properties to
values larger than the default to minimize calls to the Web service.
Handle the Populating event and set the PopulatingEventArgs.Cancel
property to true.
Do the necessary processing and set the ItemsSource property to the
desired item collection.
Call the PopulateComplete method to signal the AutoCompleteBox to show
the drop-down.
Now I see a big problem with the last step because I don't know how I can call a method on a view from the view model, provided they don't know (and are not supposed to know!) anything about each other.
So how on earth am I supposed to get that PopulateComplete method of view called from the view model without breaking MVVM principles?
If you use Blend's Interactivity library, one option is an attached Behavior<T> for the AutoCompleteBox:
public class AsyncAutoCompleteBehavior : Behavior<AutoCompleteBox>
{
public static readonly DependencyProperty SearchCommandProperty
= DependencyProperty.Register("SearchCommand", typeof(ICommand),
typeof(AsyncAutoCompleteBehavior), new PropertyMetadata(null));
public ICommand SearchCommand
{
get { return (ICommand)this.GetValue(SearchCommandProperty); }
set { this.SetValue(SearchCommandProperty, value); }
}
protected override void OnAttached()
{
this.AssociatedObject.Populating += this.PopulatingHook;
}
protected override void OnDetaching()
{
this.AssociatedObject.Populating -= this.PopulatingHook;
}
private void PopulatingHook(object sender, PopulatingEventArgs e)
{
var command = this.SearchCommand;
var parameter = new SearchCommandParameter(
() => this.AssociatedObject
.Dispatcher
.BeginInvoke(this.AssociatedObject.PopulateComplete),
e.Parameter);
if (command != null && command.CanExecute(parameter))
{
// Cancel the pop-up, execute our command which calls
// parameter.Complete when it finishes
e.Cancel = true;
this.SearchCommand.Execute(parameter);
}
}
}
Using the following parameter class:
public class SearchCommandParameter
{
public Action Complete
{
get;
private set;
}
public string SearchText
{
get;
private set;
}
public SearchCommandParameter(Action complete, string text)
{
this.Complete = complete;
this.SearchText = text;
}
}
At this point you need to do 2 things:
Wire up the Behavior
<sdk:AutoCompleteBox MinimumPopulateDelay="250" MinimumPrefixLength="2" FilterMode="None">
<i:Interaction.Behaviors>
<b:AsyncAutoCompleteBehavior SearchCommand="{Binding Search}" />
</i:Interaction.Behaviors>
</sdk:AutoCompleteBox>
Create a DelegateCommand which handles your aysnc searching.
public class MyViewModel : ViewModelBase
{
public ICommand Search
{
get;
private set;
}
private void InitializeCommands()
{
this.Search = new DelegateCommand<SearchCommandParamater>(DoSearch);
}
private void DoSearch(SearchCommandParameter parameter)
{
var client = new WebClient();
var uri = new Uri(
#"http://www.example.com/?q="
+ HttpUtility.UrlEncode(parameter.SearchText));
client.DownloadStringCompleted += Downloaded;
client.DownloadStringAsync(uri, parameter);
}
private void Downloaded(object sender, DownloadStringCompletedEventArgs e)
{
// Do Something with 'e.Result'
((SearchCommandParameter)e.UserState).Complete();
}
}

How do I find the output model type in a behavior?

With FubuMVC, I'm not sure what the best way is to determine the current action's output model type. I see different objects that I could get the current request's URL from. But that doesn't lead to a very good solution.
What's the easiest way to get the current action's output model type from the behavior?
If this isn't a good practice, what's a better way?
First, I'm assuming you've already got your settings object(s) set up in StructureMap and have the ISettingsProvider stuff already wired up.
The best, simplest thing to do would be just to pull the settings in the view, like this:
<%: Get<YourSettingsObject>().SomeSettingProperty %>
If you insist on having these be a property on your output model, then continue reading:
Let's say you had a settings object like this:
public class OutputModelSettings
{
public string FavoriteAnimalName { get; set; }
public string BestSimpsonsCharacter { get; set; }
}
Then you had an output model like this:
public class OutputModelWithSettings
{
public string SomeOtherProperty { get; set; }
public OutputModelSettings Settings { get; set; }
}
You'll need to do a few things:
Wire up StructureMap so that it will do setter injection for Settings objects (so it will automatically inject the OutputModelSettings into your output model's "Settings" property.
Set up a setter injection policy in your StructureMap initialization code (a Registry, Global ASAX, your Bootstrapper, etc -- wherever you set up your container).
x.SetAllProperties(s => s.Matching(p => p.Name.EndsWith("Settings")));
Create your behavior to call StructureMap's "BuildUp()" on the output model to trigger the setter injection. The behavior will be an open type (i.e. on the end) so that it can support any kind of output model
public class OutputModelSettingBehavior<TOutputModel> : BasicBehavior
where TOutputModel : class
{
private readonly IFubuRequest _request;
private readonly IContainer _container;
public OutputModelSettingBehavior(IFubuRequest request, IContainer container)
: base(PartialBehavior.Executes)
{
_request = request;
_container = container;
}
protected override DoNext performInvoke()
{
BindSettingsProperties();
return DoNext.Continue;
}
public void BindSettingsProperties()
{
var viewModel = _request.Find<TOutputModel>().First();
_container.BuildUp(viewModel);
}
}
Create a convention to wire up the behavior
public class OutputModelSettingBehaviorConfiguration : IConfigurationAction
{
public void Configure(BehaviorGraph graph)
{
graph.Actions()
.Where(x => x.HasOutput &&
x.OutputType().GetProperties()
.Any(p => p.Name.EndsWith("Settings")))
.Each(x => x.AddAfter(new Wrapper(
typeof (OutputModelSettingBehavior<>)
.MakeGenericType(x.OutputType()))));
}
}
Wire the convention into your FubuRegistry after the Routes section:
ApplyConvention<OutputModelSettingBehaviorConfiguration>();
In your view, use the new settings object:
<%: Model.Settings.BestSimpsonsCharacter %>
NOTE: I have committed this as a working sample in the FubuMVC.HelloWorld project in the Fubu source. See this commit: https://github.com/DarthFubuMVC/fubumvc/commit/2e7ea30391eac0053300ec0f6f63136503b16cca

asp mvc roles from httpcontext

i want to pass current area name to authorization attribute, like:
[SexyAuthorize(Roles = Url.RequestContext.RouteData.Values["area"])]
public class FormsController : Controller
{
}
but Url is member of controller. how can i pass it other way?
i know that i can use User.InRole in each method, but i want do it for class. thx.
You can't pass dynamic values to an attribute like this. All values passed to an attribute in .NET need to be known at compile time. One possible workaround is to fetch this value in your custom implementation of the attribute as you have access to the HTTP context.
Something like:
[SexyAuthorize(RolesRouteParamName = "area")]
public class FormsController : Controller
{
...
}
and then:
public SexyAuthorizeAttribute: AuthorizeAttribute
{
public string RolesRouteParamName { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
var roles = httpContext.Request.RequestContext.RouteData.Value[RolesRouteParamName];
// TODO: continue with the implementation...
...
}
}