c# Setting / Getting properties of controls to / from another thread - multithreading

I have this code for setting controls parameter to another thread:
private delegate void SetPropertySafeDelegate<TResult>(System.Windows.Forms.Control #this, Expression<Func<TResult>> property, TResult value);
public static void SetProperty<TResult>(this System.Windows.Forms.Control #this, Expression<Func<TResult>> property, TResult value)
{
var propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo;
if (propertyInfo == null || !#this.GetType().IsSubclassOf(propertyInfo.ReflectedType) || #this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}
if (#this.InvokeRequired)
{
#this.Invoke(new SetPropertySafeDelegate<TResult>(SetProperty), new object[] { #this, property, value });
}
else
{
#this.GetType().InvokeMember(propertyInfo.Name, BindingFlags.SetProperty, null, #this, new object[] { value });
}
}
it works like this:
label1.SetProperty(() => label1.Text, "xxx");
but I need it to work also on other things, such as:
checkBox4.SetProperty(() => checkBox4.Checked, true);
which doesn't work.
The second thing I need is the same function for getting control value.
Thanks a lot for your advice.

The solution which works for me:
/// <summary>
/// Gets control property. Usage: label1.GetProperty2(() => label1.Text);
/// </summary>
public static object GetProperty2<TResult>(this Control #this, Expression<Func<TResult>> property)
{
var propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo;
return #this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType).GetValue(#this, null);
}
/// <summary>
/// Sets control property. Usage: label1.SetProperty2(() => label1.Text, "Zadej cestu k modelu.");
/// </summary>
public static void SetProperty2<TResult>(this Control #this, Expression<Func<TResult>> property, TResult value)
{
var propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo;
if (#this.InvokeRequired)
{
#this.Invoke(new SetPropertySafeDelegate<TResult>(SetProperty2), new object[] { #this, property, value });
}
else
{
#this.GetType().InvokeMember(propertyInfo.Name, BindingFlags.SetProperty, null, #this, new object[] { value });
}
}
private delegate void SetPropertySafeDelegate<TResult>(Control #this, Expression<Func<TResult>> property, TResult value);

Related

How to Skip Empty Collections in Yamldotnet

I'm trying to figure out how to skip serializing empty collections using YamlDotNet. I have experimented with both a custom ChainedObjectGraphVisitor and IYamlTypeConverter. I'm new to using YamlDotNet and have some knowledge gaps here.
Below is my implementation for the visitor pattern, which results in a YamlDotNet.Core.YamlException "Expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS, got MappingEnd" error. I do see some online content for MappingStart/MappingEnd, but I'm not sure how it fits into what I'm trying to do (eliminate clutter from lots of empty collections). Any pointers in the right direction are appreciated.
Instantiating the serializer:
var serializer = new YamlDotNet.Serialization.SerializerBuilder()
.WithNamingConvention(new YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention())
.WithEmissionPhaseObjectGraphVisitor(args => new YamlIEnumerableSkipEmptyObjectGraphVisitor(args.InnerVisitor))
.Build();
ChainedObjectGraphVisitor implementation:
public sealed class YamlIEnumerableSkipEmptyObjectGraphVisitor : ChainedObjectGraphVisitor
{
public YamlIEnumerableSkipEmptyObjectGraphVisitor(IObjectGraphVisitor<IEmitter> nextVisitor)
: base(nextVisitor)
{
}
public override bool Enter(IObjectDescriptor value, IEmitter context)
{
bool retVal;
if (typeof(System.Collections.IEnumerable).IsAssignableFrom(value.Value.GetType()))
{ // We have a collection
var enumerableObject = (System.Collections.IEnumerable)value.Value;
if (enumerableObject.GetEnumerator().MoveNext()) // Returns true if the collection is not empty.
{ // Serialize it as normal.
retVal = base.Enter(value, context);
}
else
{ // Skip this item.
retVal = false;
}
}
else
{ // Not a collection, normal serialization.
retVal = base.Enter(value, context);
}
return retVal;
}
}
I believe the answer is to also override the EnterMapping() method in the base class with logic that is similar to what was done in the Enter() method:
public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
{
bool retVal = false;
if (value.Value == null)
return retVal;
if (typeof(System.Collections.IEnumerable).IsAssignableFrom(value.Value.GetType()))
{ // We have a collection
var enumerableObject = (System.Collections.IEnumerable)value.Value;
if (enumerableObject.GetEnumerator().MoveNext()) // Returns true if the collection is not empty.
{ // Don't skip this item - serialize it as normal.
retVal = base.EnterMapping(key, value, context);
}
// Else we have an empty collection and the initialized return value of false is correct.
}
else
{ // Not a collection, normal serialization.
retVal = base.EnterMapping(key, value, context);
}
return retVal;
}
I ended up with the following class:
using System.Collections;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.ObjectGraphVisitors;
sealed class YamlIEnumerableSkipEmptyObjectGraphVisitor : ChainedObjectGraphVisitor
{
public YamlIEnumerableSkipEmptyObjectGraphVisitor(IObjectGraphVisitor<IEmitter> nextVisitor): base(nextVisitor)
{
}
private bool IsEmptyCollection(IObjectDescriptor value)
{
if (value.Value == null)
return true;
if (typeof(IEnumerable).IsAssignableFrom(value.Value.GetType()))
return !((IEnumerable)value.Value).GetEnumerator().MoveNext();
return false;
}
public override bool Enter(IObjectDescriptor value, IEmitter context)
{
if (IsEmptyCollection(value))
return false;
return base.Enter(value, context);
}
public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
{
if (IsEmptyCollection(value))
return false;
return base.EnterMapping(key, value, context);
}
}
you can specify DefaultValuesHandling
in the serializer:
var serializer = new SerializerBuilder()
.ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitEmptyCollections)
.Build();
or in an attribute YamlMember for a field/property:
public class MyDtoClass
{
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)]
public List<string> MyCollection;
}

Getter in an interface with default method JSF

I have an interface with the following default method:
default Integer getCurrentYear() {return DateUtil.getYear();}
I also have a controller that implements this interface, but it does not overwrite the method.
public class NotifyController implements INotifyController
I'm trying to access this method from my xhtml like this:
#{notifyController.currentYear}
However when I open the screen the following error occurs:
The class 'br.com.viasoft.controller.notify.notifyController' does not have the property 'anoAtual'
If I access this method from an instance of my controller, it returns the right value, however when I try to access it from my xhtml as a "property" it occurs this error.
Is there a way to access this interface property from a reference from my controller without having to implement the method?
This may be considered as a bug, or one might argue it is a decision to not support default methods as properties.
See in JDK8 java.beans.Introspector.getPublicDeclaredMethods(Class<?>)
or in JDK13 com.sun.beans.introspect.MethodInfo.get(Class<?>)
at line if (!method.getDeclaringClass().equals(clz))
And only the super class (recursively upto Object, but not the interfaces) are added, see java.beans.Introspector.Introspector(Class<?>, Class<?>, int) when setting superBeanInfo.
Solutions:
Use EL method call syntax (i.e. not property access): #{notifyController.getCurrentYear()} in your case.
Downside: You have to change the JSF code and must consider for each use if it may be a default method. Also refactoring forces changes that are not recognized by the compiler, only during runtime.
Create an EL-Resolver to generically support default methods. But this should use good internal caching like the standard java.beans.Introspector to not slow down the EL parsing.
See "Property not found on type" when using interface default methods in JSP EL for a basic example (without caching).
If only a few classes/interfaces are affected simply create small BeanInfo classes.
The code example below shows this (basing on your example).
Downside: A separate class must be created for each class (that is used in JSF/EL) implementing such an interface.
See also: Default method in interface in Java 8 and Bean Info Introspector
=> static getBeanInfo() in the interface with default methods
=> simple+short BeanInfo class for each class extending the interface
interface INotifyController {
default Integer getCurrentYear() { ... }
default boolean isAHappyYear() { ... }
default void setSomething(String param) { ... }
/** Support for JSF-EL/Beans to get default methods. */
static java.beans.BeanInfo[] getBeanInfo() {
try {
java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(INotifyController.class);
if (info != null) return new java.beans.BeanInfo[] { info };
} catch (java.beans.IntrospectionException e) {
//nothing to do
}
return null;
}
}
public class NotifyController implements INotifyController {
// your class implementation
...
}
// must be a public class and thus in its own file
public class NotifyControllerBeanInfo extends java.beans.SimpleBeanInfo {
#Override
public java.beans.BeanInfo[] getAdditionalBeanInfo() {
return INotifyController.getBeanInfo();
}
}
I found it will be fixed in Jakarta EE 10.
https://github.com/eclipse-ee4j/el-ri/issues/43
Before Jakarta EE 10 you can use custom EL Resolver.
package ru.example.el;
import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ELResolver;
import java.beans.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class DefaultMethodELResolver extends ELResolver {
private static final Map<Class<?>, BeanProperties> properties = new ConcurrentHashMap<>();
#Override
public Object getValue(ELContext context, Object base, Object property) {
if (base == null || property == null) {
return null;
}
BeanProperty beanProperty = getBeanProperty(base, property);
if (beanProperty != null) {
Method method = beanProperty.getReadMethod();
if (method == null) {
throw new ELException(String.format("Read method for property '%s' not found", property));
}
Object value;
try {
value = method.invoke(base);
context.setPropertyResolved(base, property);
} catch (Exception e) {
throw new ELException(String.format("Read error for property '%s' in class '%s'", property, base.getClass()), e);
}
return value;
}
return null;
}
#Override
public Class<?> getType(ELContext context, Object base, Object property) {
if (base == null || property == null) {
return null;
}
BeanProperty beanProperty = getBeanProperty(base, property);
if (beanProperty != null) {
context.setPropertyResolved(true);
return beanProperty.getPropertyType();
}
return null;
}
#Override
public void setValue(ELContext context, Object base, Object property, Object value) {
if (base == null || property == null) {
return;
}
BeanProperty beanProperty = getBeanProperty(base, property);
if (beanProperty != null) {
Method method = beanProperty.getWriteMethod();
if (method == null) {
throw new ELException(String.format("Write method for property '%s' not found", property));
}
try {
method.invoke(base, value);
context.setPropertyResolved(base, property);
} catch (Exception e) {
throw new ELException(String.format("Write error for property '%s' in class '%s'", property, base.getClass()), e);
}
}
}
#Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
if (base == null || property == null) {
return false;
}
BeanProperty beanProperty = getBeanProperty(base, property);
if (beanProperty != null) {
context.setPropertyResolved(true);
return beanProperty.isReadOnly();
}
return false;
}
#Override
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
return null;
}
#Override
public Class<?> getCommonPropertyType(ELContext context, Object base) {
return Object.class;
}
private BeanProperty getBeanProperty(Object base, Object property) {
return properties.computeIfAbsent(base.getClass(), BeanProperties::new)
.getBeanProperty(property);
}
private static final class BeanProperties {
private final Map<String, BeanProperty> propertyByName = new HashMap<>();
public BeanProperties(Class<?> cls) {
try {
scanInterfaces(cls);
} catch (IntrospectionException e) {
throw new ELException(e);
}
}
private void scanInterfaces(Class<?> cls) throws IntrospectionException {
for (Class<?> ifc : cls.getInterfaces()) {
processInterface(ifc);
}
Class<?> superclass = cls.getSuperclass();
if (superclass != null) {
scanInterfaces(superclass);
}
}
private void processInterface(Class<?> ifc) throws IntrospectionException {
BeanInfo info = Introspector.getBeanInfo(ifc);
for (PropertyDescriptor propertyDescriptor : info.getPropertyDescriptors()) {
String propertyName = propertyDescriptor.getName();
BeanProperty beanProperty = propertyByName
.computeIfAbsent(propertyName, key -> new BeanProperty(propertyDescriptor.getPropertyType()));
if (beanProperty.getReadMethod() == null && propertyDescriptor.getReadMethod() != null) {
beanProperty.setReadMethod(propertyDescriptor.getReadMethod());
}
if (beanProperty.getWriteMethod() == null && propertyDescriptor.getWriteMethod() != null) {
beanProperty.setWriteMethod(propertyDescriptor.getWriteMethod());
}
}
for (Class<?> parentIfc : ifc.getInterfaces()) {
processInterface(parentIfc);
}
}
public BeanProperty getBeanProperty(Object property) {
return propertyByName.get(property.toString());
}
}
private static final class BeanProperty {
private final Class<?> propertyType;
private Method readMethod;
private Method writeMethod;
public BeanProperty(Class<?> propertyType) {
this.propertyType = propertyType;
}
public Class<?> getPropertyType() {
return propertyType;
}
public boolean isReadOnly() {
return getWriteMethod() == null;
}
public Method getReadMethod() {
return readMethod;
}
public void setReadMethod(Method readMethod) {
this.readMethod = readMethod;
}
public Method getWriteMethod() {
return writeMethod;
}
public void setWriteMethod(Method writeMethod) {
this.writeMethod = writeMethod;
}
}
}
You should register EL Resolver in faces-config.xml.
<?xml version="1.0" encoding="utf-8"?>
<faces-config version="2.3" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd">
<name>el_resolver</name>
<application>
<el-resolver>ru.example.el.DefaultMethodELResolver</el-resolver>
</application>
</faces-config>
since this bug is related to JDK, you'll have to create a delegate method in the class that needs the property.

Entity Framework The context cannot be used while the model is being created

My unit of work class is mentioned below and I am using Ninject and I have tried injecting IUnitOfWork per request per thread scope, transient etc. but I am still getting error which is:
"Message":"An error has occurred.","ExceptionMessage":"The context cannot be used while the model is being created. This exception may be thrown if the context is used inside the OnModelCreating method or if the same context instance is accessed by multiple threads concurrently. Note that instance members of DbContext and related classes are not guaranteed to be thread safe.","ExceptionType":"System.InvalidOperationException
I get this error when i make two web API (get) calls at the same time using angularJS and it shows error at the point _context.Set<TEntity>().FirstOrDefault(match);
public class UnitOfWork : IUnitOfWork, IDisposable
{
private My_PromotoolEntities _uowDbContext = new My_PromotoolEntities();
private Dictionary<string, object> _repositories;
// Do it like this if no specific class file
private GenericRepository<MysPerson> _personRepository;
//private GenericRepository<MysDataSource> dataSourcesRepository;
//private GenericRepository<MysCountry> countryMasterRepository;
// Or like this if with specific class file.
private DataSourceRepository _dataSourcesRepository;
private CustomerRepository _customerRepository;
private DeviceRepository _deviceRepository;
private DeviceRegistrationRepository _deviceRegistrationRepository;
private EmailQueueRepository _emailQueueRepository;
public void SetContext(My_PromotoolEntities context)
{
_uowDbContext = context;
}
public void CacheThis(object cacheThis, string keyName, TimeSpan howLong)
{
Cacheing.StaticData.CacheStaticData(cacheThis, keyName, howLong);
}
public object GetFromCache(string keyName)
{
return Cacheing.StaticData.GetFromCache(keyName);
}
public GenericRepository<T> GenericRepository<T>() where T : BaseEntity
{
if (_repositories == null)
{
_repositories = new Dictionary<string, object>();
}
var type = typeof(T).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), _uowDbContext);
_repositories.Add(type, repositoryInstance);
}
return (GenericRepository<T>)_repositories[type];
}
public GenericRepository<MysPerson> PersonRepository
{
get
{
if (this._personRepository == null)
{
this._personRepository = new GenericRepository<MysPerson>(_uowDbContext);
}
return _personRepository;
}
}
public DataSourceRepository DataSourcesRepository
{
get
{
if (this._dataSourcesRepository == null)
{
this._dataSourcesRepository = new DataSourceRepository(_uowDbContext);
}
return _dataSourcesRepository;
}
}
public CustomerRepository CustomerRepository
{
get
{
if (this._customerRepository == null)
{
this._customerRepository = new CustomerRepository(_uowDbContext);
}
return _customerRepository;
}
}
public DeviceRepository DeviceRepository
{
get
{
if (this._deviceRepository == null)
{
this._deviceRepository = new DeviceRepository(_uowDbContext);
}
return _deviceRepository;
}
}
public DeviceRegistrationRepository DeviceRegistrationRepository
{
get
{
if (this._deviceRegistrationRepository == null)
{
this._deviceRegistrationRepository = new DeviceRegistrationRepository(_uowDbContext);
}
return _deviceRegistrationRepository;
}
}
public EmailQueueRepository emailQueueRepository
{
get
{
if (this._emailQueueRepository == null)
{
this._emailQueueRepository = new EmailQueueRepository(_uowDbContext);
}
return _emailQueueRepository;
}
}
/// <summary>
/// Commits all changes to the db. Throws exception if fails. Call should be in a try..catch.
/// </summary>
public void Save()
{
try
{
_uowDbContext.SaveChanges();
}
catch (DbEntityValidationException dbevex)
{
// Entity Framework specific errors:
StringBuilder sb = new StringBuilder();
var eve = GetValidationErrors();
if (eve.Count() > 0)
{
eve.ForEach(error => sb.AppendLine(error));
}
ClearContext();
// Throw a new exception with original as inner.
var ex = new Exception(sb.ToString(), dbevex);
ex.Source = "DbEntityValidationException";
throw ex;
}
catch (Exception)
{
ClearContext();
throw;
}
}
private void ClearContext()
{
DetachAll();
}
private void DetachAll()
{
foreach (DbEntityEntry dbEntityEntry in _uowDbContext.ChangeTracker.Entries())
{
if (dbEntityEntry.Entity != null)
{
dbEntityEntry.State = EntityState.Detached;
}
}
}
/// <summary>
/// Checks for EF DbEntityValidationException(s).
/// </summary>
/// <returns>Returns a List of string containing the EF DbEntityValidationException(s).</returns>
public List<string> GetValidationErrors()
{
if (_uowDbContext.GetValidationErrors().Count() != 0)
{
return _uowDbContext.GetValidationErrors().Select(e => string.Join(Environment.NewLine, e.ValidationErrors.Select(v => string.Format("{0} - {1}", v.PropertyName, v.ErrorMessage)))).ToList();
}
return null;
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_uowDbContext.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
You should never use a context in 2 places at the same time, that's exactly why you are getting this error. From the MSDN documentation:
Thread Safety: Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
It is a little hard to make suggestions without a repro but there is a brute force approach that should resolve the issue. If you have an interception point before/during DI setup then you can cause all the context initialization etc to happen by creating an instance of your context and calling ctx.Database.Initialize(force: false); Passing 'force: false' will ensure that the initialization still only happens once per AppDomain

Automapper: Mapping using attributes for non-matching properties

I have a scenario in which I'm creating a mapping utility through which we can map DataReader to entities.
Mapper.CreateMap<IDataReader, T>();
List<T> tList = Mapper.Map<List<T>>(reader);
return tList;
This is working fine if the entity has same columns as in the stored procedure's output. However, I'm working on an some old code where column names are not matching. So I created an attribute through which I could specify corresponding column from sprocs. I specify them using code like this
[DBColumn('EmployeeName')]
public string EmpName { get; set; }
Is there any way I can achieve this using generics/reflection? Note that I have multiple such instances and I'm building an utility. So I can not use ForMember method to achieve that.
UPDATE: I tried creating a custom converter like this. However, my convert function is not getting called at all.
Custom converter
public class CustomConverter<T> : ITypeConverter<System.Data.IDataReader, T>
{
private ResolutionContext _Context = null;
private Dictionary<string, string> _CustomProps { get; set; }
public T Convert(ResolutionContext context)
{
_Context = context;
if (_Context.SourceValue != null &&
!(_Context.SourceValue is System.Data.IDataReader))
{
string message = "Value supplied is of type {0} but expected {1}.\n" +
"Change the type converter source type, or redirect " +
"the source value supplied to the value resolver using FromMember.";
throw new AutoMapperMappingException(_Context, string.Format(
message, typeof(System.Data.IDataReader), _Context.SourceValue.GetType()));
}
_CustomProps = new Dictionary<string, string>();
foreach (PropertyInfo propInfo in context.DestinationType.GetProperties())
{
if (propInfo.CustomAttributes.Any(attr => attr.AttributeType == typeof(DBColumn)))
{
DBColumn propertyValue = (DBColumn)propInfo.GetCustomAttribute(typeof(DBColumn));
_CustomProps.Add(propertyValue.ColumnName, propInfo.Name);
}
}
//return base.Convert(context);
return ConvertCore((System.Data.IDataReader)context.SourceValue);
}
protected T ExistingDestination
{
get
{
if (_Context == null)
{
string message = "ResolutionContext is not yet set. " +
"Only call this property inside the 'ConvertCore' method.";
throw new InvalidOperationException(message);
}
if (_Context.DestinationValue != null &&
!(_Context.DestinationValue is T))
{
string message = "Destination Value is of type {0} but expected {1}.";
throw new AutoMapperMappingException(_Context, string.Format(
message, typeof(T), _Context.DestinationValue.GetType()));
}
return (T)_Context.DestinationValue;
}
}
protected T ConvertCore(System.Data.IDataReader source)
{
T obj = ExistingDestination;
if (obj != null)
{
foreach (KeyValuePair<string, string> keyValuePair in _CustomProps)
{
PropertyInfo prop = obj.GetType().GetProperty(keyValuePair.Key, BindingFlags.Public | BindingFlags.Instance);
if (null != prop && prop.CanWrite)
{
prop.SetValue(obj, System.Convert.ChangeType(source[keyValuePair.Value], prop.PropertyType));
}
}
}
return obj;
}
I'm specifying to use this CustomConverter in below statement.
Mapper.CreateMap<IDataReader, T>().ConvertUsing<CustomConverter<T>>();
List<T> tList = Mapper.Map<List<T>>(reader);
return tList;
Please let me know, where I'm going wrong

MvvmLight IDataService with async await

I'm trying to find a clean way to accomplish MvvmLight's IDataService pattern with async/await.
Originally I was using the callback Action method to work similar to the template's but that doesn't update the UI either.
public interface IDataService
{
void GetData(Action<DataItem, Exception> callback);
void GetLocationAsync(Action<Geoposition, Exception> callback);
}
public class DataService : IDataService
{
public void GetData(Action<DataItem, Exception> callback)
{
// Use this to connect to the actual data service
var item = new DataItem("Location App");
callback(item, null);
}
public async void GetLocationAsync(Action<Geoposition, Exception> callback)
{
Windows.Devices.Geolocation.Geolocator locator = new Windows.Devices.Geolocation.Geolocator();
var location = await locator.GetGeopositionAsync();
callback(location, null);
}
}
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
private string _locationString = string.Empty;
public string LocationString
{
get
{
return _locationString;
}
set
{
if (_locationString == value)
{
return;
}
_locationString = value;
RaisePropertyChanged(LocationString);
}
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
_dataService.GetLocation(
(location, error) =>
{
LocationString = string.Format("({0}, {1})",
location.Coordinate.Latitude,
location.Coordinate.Longitude);
});
}
}
I'm trying to databind against gps coordinates but since the async fires and doesn't run on main thread it doesn't update the UI.
Might be unrelated, but AFAICT you're missing some quotes
RaisePropertyChanged(LocationString);
You pass the name of the property that changed, not its value.

Resources