Blazor: How to pass a parameter via URL to the ViewModel (not to the page/view) - mvvm

I know how to pass a parameter from the url to a blazor page like this:
#page "/myPage/{myParameter}"
#if(myParameter != null && myParameter != "")
{
<p>#myParameter</p>
}
#code{
[Parameter]
public string myParameter {get; set;};
}
If I would enter something like this in the browser: www.xyz.com/mypage/TEST
I could show 'TEST' on my page. But how can I pass a parameter directly to my injected ViewModel?
This doesn't work #page "/mypage/{ViewModel.myParameter}". The ViewModel is injected via Startup.cs and as a services.AddScoped.
I have a 'workaround':
#code {
[Parameter]
public string myParameter { get; set; }
protected override void OnInitialized() //On Page Load
{
ViewModel.myParameter = myParameter;
}
}
Is there a proper way to do this? Thx for help (still new to blazor)

MyParameter is extracted from the Route by the Router and passed into the page component as a Parameter in SetParametersAsync. In theory you could put in on the setter for myParameter. Don't - this is very definitely not recommended practice.
Also, Lets say you're on "/mypage/1" and you navigate to "/mypage/2", OnInitialized won't be called. You may think you're navigating between pages, but in reality, your just calling SetParametersAsync on the same component with a new value for MyParameter.
Therefore something like:
protected override void OnParametersSet()
{
if (!ViewModel.myParameter.Equals(myParameter)) ViewModel.myParameter = myParameter;
}
will ensure it is set if it changes, otherwise not (I don't know what getters/setters are on myParameter and what setting it every time on OnParametersSet precipitates!).

Related

Passing parent component to all child components in blazor via rendertree

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>

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!

How to pass dynamic variable to Action Filter in ASP.NET MVC

I would like to use a variable to pass a dynamic value to my action filter. I thought it would be something like this:
[MessageActionFilter(message = "User is updating item: " & id)]
public ActionResult doSomething(int id)
{
// do something
}
However, it seems that the parameter must be a constant value. Therefore, my question is how do I get the variable to my action filter?
You can get the parameter values in OnActionExecuting using the ActionExecutingContext.ActionParameters property.
It's just a pseudo code, but for example you can retrive the parameter named id
public class MessageActionFilter: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var response = filterContext.HttpContext.Response;
var parameterValue = filterContext.ActionParameters.SingleOrDefault(p => p.Key == "id");
// check if not null before writing a message
response.Write(this.Message + parameterValue); // prints "User is updating item: <idvalue>"
}
public string Message {get; set;}
}
Tell me if it helps.

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...
...
}
}