I have a UWP application which uses the Managed UWP Behavior SDK.
I wrote a custom behavior which has two dependency properties, one of which is a ObservableCollection.
Whenever I update an item in the collection, I make sure that PropertyChanged is called for the collection.
However, the Dependency property is not being updated.
My code:
<trigger:CustomBehavior ItemIndex="{x:Bind ItemIndex}"
Presences="{Binding ElementName=Box,
Path=DataContext.CustomCollection,
UpdateSourceTrigger=PropertyChanged, Converter={StaticResource TestConverter}}" />
My TestConverter shows me that when I update an item in the collection, the updatesource trigger is working. The dependency property in my behavior however, is not firing the Changed event. When I change the entire custom collection, the DP is updated, when I just change one item, it isn't.
Research so far says that DependencyObject.SetValue just checks to see if the object has changed and if one item changed, it will just think that the collection didn't change at all? Is this true, and if so, how can I overcome this?
Thanks
A collection-type dependency property should usually be declared as the most basic collection type, IEnumerable. This way you can assign a variety of actual collection types to the property, including those that implement INotifyCollectionChanged, like ObservableCollection<T>.
You would check at runtime if the collection type actually implements the interface, and possibly attach and detach a handler method for the CollectionChanged event.
public class CustomBehavior : ...
{
public static readonly DependencyProperty PresencesProperty =
DependencyProperty.Register(
"Presences", typeof(IEnumerable), typeof(CustomBehavior),
new PropertyMetadata(null,
(o, e) => ((CustomBehavior)o).OnPresencesPropertyChanged(e)));
private void OnPresencesPropertyChanged(DependencyPropertyChangedEventArgs e)
{
var oldCollectionChanged = e.OldValue as INotifyCollectionChanged;
var newCollectionChanged = e.NewValue as INotifyCollectionChanged;
if (oldCollectionChanged != null)
{
oldCollectionChanged.CollectionChanged -= OnPresencesCollectionChanged;
}
if (newCollectionChanged != null)
{
newCollectionChanged.CollectionChanged += OnPresencesCollectionChanged;
// in addition to adding a CollectionChanged handler, any
// already existing collection elements should be processed here
}
}
private void OnPresencesCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
// handle collection changes here
}
}
Related
I don't quite fully understand this situation, where AsyncLocal instance is set at a certain point in the AuthenticationHandler, but does not reach the controller, when it is injected into the constructor.
I've made it similar to how IHttpContextAccessor works, but still nowhere near. However, if I set the AsyncLocal from a Middleware, it reaches the controller. Also, setting the HttpContext.Items property from AuthenticationHandler works just fine.
Question: How is HttpContext able to retain Items property contents all the way, and is ASP.NET runtime disposing the captured ExecutionContext of my DomainContextAccessor for some security reason because of where it is being set at?
I've made a sample app to demo this use case. I'd really appreciate someone shedding the light on this problem.
You already have a good answer on "how should I fix this?" Here's more of a description of why it's behaving this way.
AsyncLocal<T> has the same semantics as logging scopes. Because it has those same semantics, I always prefer to use it with an IDisposable, so that the scope is clear and explicit, and there's no weird rules around whether a method is marked async or not.
For specifics on the weird rules, see this. In summary:
Writing a new value to an AsyncLocal<T> sets that value in the current scope.
Methods marked async will copy their scope to a new scope the first time it's written to (and it's the new scope that is modified).
I've made it similar to how IHttpContextAccessor works, but still nowhere near.
I don't recommend copying the design of IHttpContextAccessor. It works... for that very specific use case. If you want to use AsyncLocal<T>, then use a design like this:
static class MyImplicitValue
{
private static readonly AsyncLocal<T> Value = new();
public static T Get() => Value.Value;
public static IDisposable Set(T newValue)
{
var oldValue = Value.Value;
Value.Value = newValue;
return new Disposable(() => Value.Value = oldValue);
}
}
usage:
using (MyImplicitValue.Set(myValue))
{
// Code in here can get myValue from MyImplicitValue.Get().
}
You can wrap that into an IMyImplicitValueAccessor if desired, but note that any "setter" logic should be using the IDisposable pattern as shown.
AsyncLocal instance is set at a certain point in the AuthenticationHandler, but does not reach the controller
That's because your AuthenticationHandler sets the value but doesn't call the controller after setting that value (and it shouldn't).
However, if I set the AsyncLocal from a Middleware, it reaches the controller.
That's because middleware is calls the next middleware (eventually getting to the controller). I.e., middleware is structured like this:
public async Task InvokeAsync(HttpContext context)
{
using (implicitValue.Set(myValue))
{
await _next(context);
}
}
So the controllers are in the scope of when that AsyncLocal<T> value was set.
How is HttpContext able to retain Items property contents all the way
Items is just a property bag. It doesn't have anything to do with AsyncLocal<T>. It exists because it's a property on HttpContext, and it persists because the same HttpContext instance is used throughout the request.
is ASP.NET runtime disposing the captured ExecutionContext of my DomainContextAccessor for some security reason because of where it is being set at?
Not exactly. The AsyncLocal<T> is being set just fine; it's just that the controllers are not called within the scope of that AsyncLocal<T> being set.
So what must be happening is there is a execution context change which wipes that value out. It works with in the middleware because your controller is in the same execution context as your middleware.
Change your code to this:
private static void DomainContextChangeHandler(AsyncLocalValueChangedArgs<DomainContextHolder> args)
{
Trace.WriteLine($"ThreadContextChanged: {args.ThreadContextChanged}");
Trace.WriteLine($"Current: {args.CurrentValue?.GetHashCode()}");
Trace.WriteLine($"Previous: {args.PreviousValue?.GetHashCode()}");
Trace.WriteLine($"Thread Id: {Thread.CurrentThread.ManagedThreadId}");
}
Now you can see when the context changes.
Here is something you could do:
private static void DomainContextChangeHandler(AsyncLocalValueChangedArgs<DomainContextHolder> args)
{
if (args.ThreadContextChanged && (args.PreviousValue != null) && (args.CurrentValue == null))
{
Trace.WriteLine(
"***** Detected context change with a previous value but setting current " +
"value to null. Resetting value to previous.");
_domainContextCurrent.Value = args.PreviousValue;
return;
}
Trace.WriteLine($"ThreadContextChanged: {args.ThreadContextChanged}");
Trace.WriteLine($"Current: {args.CurrentValue?.GetHashCode()}");
Trace.WriteLine($"Previous: {args.PreviousValue?.GetHashCode()}");
Trace.WriteLine($"Thread Id: {Thread.CurrentThread.ManagedThreadId}");
}
But, that kinda defeats the purpose of using AsyncLocal as your backing store.
My suggestion is you drop the AsyncLocal and use normal class-scoped storage:
namespace WebApp.Models
{
public interface IDomainContextAccessor
{
DomainContext DomainContext { get; set; }
}
public sealed class DomainContextAccessor : IDomainContextAccessor
{
public DomainContext DomainContext { get; set; }
}
}
And inject it as scoped instead of singleton:
services.AddScoped<IDomainContextAccessor, DomainContextAccessor>();
It will do exactly what you want without any kludges -- AND, the future you (or devs) will absolutely understand what's going on and why it is the way it is.
No middleware, no AsyncLocal funny-business. It just works.
Your answer is here:
.net core AsyncLocal loses its value
In your DomainContextAccessor class when you set new value in this line: _domainContextCurrent.Value = new DomainContextHolder { Context = value };
you create NEW ExecutionContext in current thread and child threads.
So I suppose that mvc runs like this:
Middleware thread => you set value => some child thread with Controller execution which sees parent changes
But for UserAuthenticationHandler it feels it works like this:
Some controller factory creates controller with injected IDomainContextAccessor (1 context) => mvc executes auth handler in child task where you set value and create 2 context. But it's value does not go UP to parent (where controller 1 context exists) because you create new context when you set value. Even more your code gets parents context, gets reference to its value and makes property Context = null, so you will get null in Controller.
So to fix this you need to change your code:
public class DomainContext
{
private static AsyncLocal<DomainContext> _contextHolder = new AsyncLocal<DomainContext>();
public static DomainContext Current
{
get
{
return _contextHolder.Value;
}
}
public Job JobInfo { get; set; }
public static void InitContext()
{
_contextHolder.Value = new DomainContext();
}
}
//using in middleware:
DomainContext.InitContext();
//using in auth handler:
DomainContext.Current.JobInfo = ...
In example above you don't change DomainContext reference in _contextHolder.Value;
It remains the same but you only change value of JobInfo in it later in auth handler
I'm making a custom TextBox for UWP to simplify Win2D outlined text solution, for that I created a UserControl that contains only a canvas on which I'll draw the text.
Of course I need some properties, like text, outline thickness and color, etc...
I also need some properties that are already exposed by the inherited UserControl like Foreground, FontSize, FontFamily...
So far so good, it seems like I won't need to implement each one of those common properties.
The problem is that I can't find a way to hook up an event when one of those properties changes, as I have to call the Canvas.Invalidate() method to redraw it when the format changes.
Looks like I have to hide all those properties and create new Dependency Properties to call Canvas.Invalidate().
There is no way to do it faster?
Nevermind, the answer was behind the corner.
In the constructor, you can call
RegisterPropertyChangedCallback(DependencyProperty dp, DependencyPropertyChangedCallback callback);
For example:
public OutlinedText()
{
InitializeComponent();
RegisterPropertyChangedCallback(FontFamilyProperty, OnPropertyChanged);
RegisterPropertyChangedCallback(FontSizeProperty, OnPropertyChanged);
}
private void OnPropertyChanged(DependencyObject sender, DependencyProperty dp)
{
OutlinedText instance = sender as OutlinedText;
if (instance != null)
{
//Caching the value into CanvasTextFormat for faster drawn execution
if (dp == FontFamilyProperty)
instance.TextFormat.FontFamily = instance.FontFamily.Source;
else if (dp == FontSizeProperty)
instance.TextFormat.FontSize = (Single)instance.FontSize;
instance.needsResourceRecreation = true;
instance.canvas.Invalidate();
}
}
In Xamarin Forms, I created a bindable property like so:
public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create("SelectedItem", typeof(MyItem), typeof(MyGrid), default(MyItem));
public MyItem SelectedItem
{
get { return (MyItem)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
Here's my constructor:
public MyView()
{
InitializeComponent();
PropertyChanged += OnPropertyChanged;
}
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
if (propertyChangedEventArgs.PropertyName == "SelectedItem")
{
// called twice
}
}
Can somebody explain why property changed event is firing twice? If I create a changed handler in the definition of the bindable property, then the handler is called once.
public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create("SelectedItem", typeof(MyItem), typeof(MyGrid), default(MyItem), null, SelectedItemChanged);
I have noticed the issue exists only in code-behind. If I set the property directly in XAML, property changed event fires once.
We don't really have enough information to answer this question with certainty, but I can speculate.
Without seeing your SetValue method, my assumption would be that it lacks a short circuit, e.g. "don't do anything if the new value and the old value are the same".
Then my second assumption would be that the control that is being bound to this property is setting it (after being bound). This can happen with list-type controls when SelectedItem is bound.
The resulting chain of events might be something like:
Code sets property
PropertyChanged event is fired
Binding sets the value on a control
Control reports it's value has been changed, and two-way binding sets the value on the ViewModel again
The lack of a short circuit causes the PropertyChanged event to be raised again
The binding sets the value on the control again (to the same value as before)
The control does not report a change, because it's property is short-circuited properly
My guess is that if you were to short circuit your setter (by checking against the existing value and bailing out if they are the same) this behavior would stop.
I have a Xamarin.Forms xaml page in which I am using a ListView to allow the user to pick a single item out of a list. I have bound the ListView's SelectedItem property to a property on my ViewModel and this works fine. As soon as the user changes the selected item the property in my viewmodel updates as well.
However, even though I initially set the property in my ViewModel to one of the values from the list, when the page loads the ListView's SelectedItem property is null, which in turn sets the ViewModel property to null as well.
What I need is the other direction, I want the ListView to initially select the item that i've set in the VM property.
I can hack together a solution by writing extra code in the code behind file to explicitly set the initial selected item, but this introduces additional properties and complexity and is quite ugly.
What is the correct way to set the initial selected item of a ListView who's selected item is bound to a viewmodel property?
-EDIT-
I was asked to provide the code that I'm using for my binding.
It's very simple, standard:
<ListView x:Name="myList" ItemsSource="{Binding Documents}" SelectedItem="{Binding SelectedDocument}">
the view model that is set as the binding context for the listview is instantiated before the page is created and looks like this:
public class DocumentSelectViewModel : ViewModelBase
{
private Document selectedDocument;
public List<Document> Documents
{
get { return CachedData.DocumentList; }
}
public Document SelectedDocument
{
get { return selectedDocument; }
set { SetProperty(ref selectedDocument, value);
}
public DocumentSelectViewModel()
{
SelectedDocuement = CachedData.DocumentList.FirstOrDefault();
}
}
SetProperty is a function which simply rasies the INotifyPropertyChanged event if the new value is different from the old one, classical binding code.
I am a little rusty on XAML but don't you need to make the binding two-way?
E.G.
{ Binding SelectedDocument, Mode=TwoWay }
As long as the SelectedDocument property change raises the INotifyPropertyChanged event then you should get the desired effect.
If you replace
public DocumentSelectViewModel()
{
SelectedDocument = CachedData.DocumentList.FirstOrDefault();
}
By
public DocumentSelectViewModel()
{
SelectedDocument = Documents.FirstOrDefault();
}
Does it work for you ?
I had a similar problem that has been resolved this way...
You can use ctor DocumentSelectViewModel for set initial value. Honestly I dont like to make some job in ctor block but Xamarin.... You dont need DocumentSelectViewModel method. It will work.
public DocumentSelectViewModel ()
{
SelectedDocument = Documents[0]; //or any your desired.
}
I'm working on a Windows 8 Store App (using the Grid App Template) and while I'm loading data from a server I want to show a ProgressRing and hide the GridView or ListView (depends on if the app is snapped or not) that will display the data once it is fully loaded.
The issue is that when the ViewModel is loading data I need to be able to change the VisualState.
I found what I thought was a solution Here, but this code will not build.
public class StateManager : DependencyObject
{
public static string GetVisualStateProperty(DependencyObject obj)
{
return (string)obj.GetValue(VisualStatePropertyProperty);
}
public static void SetVisualStateProperty(DependencyObject obj, string value)
{
obj.SetValue(VisualStatePropertyProperty, value);
}
public static readonly DependencyProperty VisualStatePropertyProperty =
DependencyProperty.RegisterAttached(
"VisualStateProperty",
typeof(string),
typeof(StateManager),
new PropertyMetadata((s, e) => //this throws the error
{
var propertyName = (string)e.NewValue;
var ctrl = s as Control;
if (ctrl == null)
throw new InvalidOperationException("This attached property only supports types derived from Control.");
System.Windows.VisualStateManager.GoToState(ctrl, (string)e.NewValue, true);
}));
}
ERROR: Cannot convert lambda expression to type 'object' because it is
not a delegate type
Does anyone know how to get the linked solution to work? Or is there a simpler method that I am completely missing (I'm a XAML newbie!)?
I'm not even sure if the listed solution will work because the "Snapped" vs "Full" states are managed by the base LayoutAwarePage class included with the template.
why not simply use a datatrigger bind to a viewmodel property like IsBusy {get;set;} to enable your Progressring?