I am trying to use Convert function from IValueConverter, but I have to call another function in it. I will use his return value but I got that error telling me to return an object value in the converter, any idea how can I avoid this please.
public void Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
RestClient client = new RestClient();
client.BaseUrl = "http://";
RestRequest request = new RestRequest();
request.Method = Method.GET;
request.AddParameter("action", "REE");
request.AddParameter("atm_longitude", location.Longitude);
client.ExecuteAsync(request, ParseFeedCallBack_ListDistance);
}
public void ParseFeedCallBack_ListDistance(IRestResponse response)
{
if (response.StatusCode == HttpStatusCode.OK)
{
ParseXMLFeedDistance(response.Content);
}
}
private string ParseXMLFeedDistance(string feed)
{
.... return myvalueToBind;
}
A simple way to calculate the distance between two coordinates, in this case, assuming you have the coordinates of the device,
using System.Device.Location;
public class GeoCalculator
{
public static double Distance(double deviceLongitude, double deviceLatitude, double atmLongitude, double atmLatitude)
{
//Coordinates of ATM (or origin).
var atmCoordinates = new GeoCoordinate(atmLatitude, atmLongitude);
//Coordinates of Device (or destination).
var deviceCordinates = new GeoCoordinate(deviceLatitude, deviceLongitude);
//Distance in meters.
return atmCoordinates.GetDistanceTo(deviceCordinates);
}
}
Hence your converter can look like:
public class DistanceConverter : IValueConverter
{
/// <summary>
/// This is your device coordinate.
/// </summary>
private static GeoCoordinate devCoordinate = new GeoCoordinate(61.1631, -149.9721);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var location = value as LocationModel;
if (location != null)
{
return GeoCalculator.Distance(devCoordinate.Longitude, devCoordinate.Latitude, location.Longitude, location.Latitude);
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Keep in mind that I would personally not use a converter for this. I would simply expose a simple property in my model that does this calculation as it's a simple logic. If you happen to be a purist and don't like any logic in your model, looping through the list and setting a property on your model would work too.
Related
I would like to partially mask the password field from dots to asterisk. I tried using a converter but it doesn't work. What is the best way to achieve this in xamarin forms.
<Entry IsPassword="True"
Placeholder="password"
Text="{Binding Password.Value, Mode=TwoWay, Converter={StaticResource
MaskedPasswordConverter}}"
MaxLength="6">
public class MaskedPasswordConverter : IValueConverter
{
private string _value;
public object Convert(object value, Type targetType, object parameter, CultureInfo
culture)
{
var str = (value ?? "").ToString();
_value = str;
var maskedStr = "";
if (!string.IsNullOrEmpty(str) && str.Length > 2)
{
var domainStr = str.IndexOf('#');
var lengthOfMask = domainStr - 2;
maskedStr = str.Substring(0, 2) + new string('*', lengthOfMask) +
str.Substring(domainStr);
}
return maskedStr;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
return value;
}
}
I suggest you use behaviors for this.
You can find out more about Xamarin forms behaviors here
More examples here
Hope this helps.
If you want to use IValueConverter to mask partial password using asterisk, I think you can set binding mode as OneWay, then please confirm that there is # character in your Password.
I suggest you can use this way to mask email, don't mask password, but you still want to do ,this is the sample that you can take a look:
<Entry
MaxLength="6"
Placeholder="password"
Text="{Binding password, Mode=OneWay, Converter={StaticResource converter1}}" />
public partial class Page24 : ContentPage, INotifyPropertyChanged
{
private string _password;
public string password
{
get
{ return _password; }
set
{
_password = value;
RaisePropertyChanged("password");
}
}
public Page24()
{
InitializeComponent();
password = "123#56";
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
IValueConverter:
public class Passwordconverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var str = (value ?? "").ToString();
var maskedStr = "";
if (!string.IsNullOrEmpty(str) && str.Length > 2)
{
var domainStr = str.IndexOf('#');
var lengthOfMask = domainStr - 2;
maskedStr = str.Substring(0, 2) + new string('*', lengthOfMask) + str.Substring(domainStr);
}
return maskedStr;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This is the screenshot:
But I still suggest you can use custom render to mask your password using asterisk, this is the sample about this, you can take a look:
How to change password masking character in Xamarin forms - Entry
I'm trying to get SkiaSharp working with Xamarin Forms and Prism. I have it working with the following page behind code
public partial class RoomLayoutPage : ContentPage
{
SKCanvasView canvasView;
public RoomLayoutPage()
{
InitializeComponent();
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
SKSurface surface = e.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKPaint blackPaint = new SKPaint
{
Color = SKColors.Black,
};
canvas.DrawRect(0, 0, 100, 100, blackPaint);
}
}
but now I want to move this to my Prism ViewModel. If I move it then Content is not valid
How do I reference a xaml element from a ViewModel? I'd prefer not to do it this way because my ViewModel is then coupled to the view.
(Prefered way) If I put an SKCanvasView on my page
I can bind to the event with the EventToCommandBehaviour
<forms:SKCanvasView>
<forms:SKCanvasView.Behaviors>
<behaviors:EventToCommandBehavior Command="{Binding OnCanvasViewPaintSurface}" EventName="PaintSurface"/>
</forms:SKCanvasView.Behaviors>
</forms:SKCanvasView>
But I'm not sure how to bind the SKPaintSurfaceEventArgs for
OnCanvasViewPaintSurface = new DelegateCommand<SKPaintSurfaceEventArgs>(OnCanvasViewPaintAction);
I'm assuming it's one of the EventArgs options from here https://prismlibrary.github.io/docs/xamarin-forms/EventToCommandBehavior.html ?
Thank you
So using a value converter solves it..
public class SkiaEventArgsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var sKPaintSurfaceEventArgs = value as SKPaintSurfaceEventArgs;
if (sKPaintSurfaceEventArgs == null)
{
throw new ArgumentException("Expected value to be of type SKPaintSurfaceEventArgs", nameof(value));
}
return sKPaintSurfaceEventArgs;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Not entirely sure why it couldn't convert it automagically but it works
In my project, I have written a custom json converter to trim the white-spaces present in the string property.
Here is an example of the typical class we will use,
public class Candidate
{
public string CandidateName { get; set; }
}
Here is my custom json converter
public class StringSanitizingConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue , JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
if (reader.Value != null)
{
string sanitizedString = (reader.Value as string).Trim();
if (StringSanitizeOptions.HasFlag(StringSanitizeOptions.ToLowerCase))
sanitizedString = sanitizedString.ToLowerInvariant();
return sanitizedString;
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var text = (string)value;
if (text == null)
writer.WriteNull();
else
writer.WriteValue(text.Trim());
}
}
With my custom converter I am now able to format the string by trimming any white-spaces present sent to the action methods using my 'Candidate' as one of its parameter.
public void Post(ComplexType complexTypeParameter){
}
Everything worked well so far. I later wanted to enhance this json converter to format the string properties based on the attributes set to the string property in the Candidate class. for example, assume I have written my candidate class like this,
public class Candidate
{
[StringSanitizingOptions(Option.ToLowerCase)]
public string CandidateName { get; set; }
}
And if I wanted to format the string properties of a class based on the custom attribute configuration inside the json converter , I am not able to access this custom attribute and its configuration inside the ReadJson method of the custom converter.
Here is what I have tried so far but with no luck,
Not present in the CustomAttributes property of the objectType
parameter sent to the ReadJson() method.
Was trying to see if I could extract the parent class of the property inside the ReadJson() method, so that I could apply reflection on the class to extract the custom attributes given to any of its property,but I could not extract that too.
The stack of containing object(s) is not made available to JsonConverter.ReadJson(), thus you cannot do what you want inside ReadJson().
Instead, what you can do is to create a custom contract resolver that applies an appropriately configured instance of StringSanitizingConverter based on the properties of the object for which a contract is being generated.
First, let's say your data model, attribute, and JsonConverter look like the following (where I had to modify a few things to make your code compile and include some additional test cases):
public class Candidate
{
[StringSanitizingOptions(Option.ToLowerCase)]
public string CandidateName { get; set; }
[StringSanitizingOptions(Option.DoNotTrim)]
public string StringLiteral { get; set; }
public string DefaultString { get; set; }
public List<string> DefaultStrings { get; set; }
}
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class StringSanitizingOptionsAttribute : System.Attribute
{
public Option StringSanitizeOptions { get; set; }
public StringSanitizingOptionsAttribute(Option stringSanitizeOptions)
{
this.StringSanitizeOptions = stringSanitizeOptions;
}
}
[Flags]
public enum Option
{
Default = 0,
ToLowerCase = (1<<0),
DoNotTrim = (1<<1),
}
public static class StringSanitizeOptionsExtensions
{
public static bool HasFlag(this Option options, Option flag)
{
return (options & flag) == flag;
}
}
public class StringSanitizingConverter : JsonConverter
{
readonly Option options;
public StringSanitizingConverter() : this(Option.Default) { }
public StringSanitizingConverter(Option options)
{
this.options = options;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
if (reader.Value != null)
{
var sanitizedString = (reader.Value as string);
if (!options.HasFlag(Option.DoNotTrim))
sanitizedString = sanitizedString.Trim();
if (options.HasFlag(Option.ToLowerCase))
sanitizedString = sanitizedString.ToLowerInvariant();
return sanitizedString;
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// WriteJson is never called with null
var text = (string)value;
if (!options.HasFlag(Option.DoNotTrim))
text = text.Trim();
writer.WriteValue(text);
}
}
Next, grab ConfigurableContractResolver from How to add metadata to describe which properties are dates in JSON.Net, and define the extension method JsonContractExtensions.AddStringConverters():
public static class JsonContractExtensions
{
public static JsonContract AddStringConverters(this JsonContract contract)
{
if (contract is JsonPrimitiveContract)
{
if (contract.UnderlyingType == typeof(string))
contract.Converter = new StringSanitizingConverter();
}
else if (contract is JsonObjectContract)
{
var objectContract = (JsonObjectContract)contract;
foreach (var property in objectContract.Properties)
{
if (property.PropertyType == typeof(string))
{
var attr = property.AttributeProvider.GetAttributes(typeof(StringSanitizingOptionsAttribute), true)
.Cast<StringSanitizingOptionsAttribute>()
.SingleOrDefault();
if (attr != null)
{
property.Converter = property.MemberConverter = new StringSanitizingConverter(attr.StringSanitizeOptions);
}
}
}
}
return contract;
}
}
public class ConfigurableContractResolver : DefaultContractResolver
{
// This contract resolver taken from the answer to
// https://stackoverflow.com/questions/46047308/how-to-add-metadata-to-describe-which-properties-are-dates-in-json-net
// https://stackoverflow.com/a/46083201/3744182
readonly object contractCreatedPadlock = new object();
event EventHandler<ContractCreatedEventArgs> contractCreated;
int contractCount = 0;
void OnContractCreated(JsonContract contract, Type objectType)
{
EventHandler<ContractCreatedEventArgs> created;
lock (contractCreatedPadlock)
{
contractCount++;
created = contractCreated;
}
if (created != null)
{
created(this, new ContractCreatedEventArgs(contract, objectType));
}
}
public event EventHandler<ContractCreatedEventArgs> ContractCreated
{
add
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be added after the first contract is generated.");
}
contractCreated += value;
}
}
remove
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be removed after the first contract is generated.");
}
contractCreated -= value;
}
}
}
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
OnContractCreated(contract, objectType);
return contract;
}
}
public class ContractCreatedEventArgs : EventArgs
{
public JsonContract Contract { get; private set; }
public Type ObjectType { get; private set; }
public ContractCreatedEventArgs(JsonContract contract, Type objectType)
{
this.Contract = contract;
this.ObjectType = objectType;
}
}
public static class ConfigurableContractResolverExtensions
{
public static ConfigurableContractResolver Configure(this ConfigurableContractResolver resolver, EventHandler<ContractCreatedEventArgs> handler)
{
if (resolver == null || handler == null)
throw new ArgumentNullException();
resolver.ContractCreated += handler;
return resolver;
}
}
Then, finally you can deserialize and serialize Candidate as follows:
var settings = new JsonSerializerSettings
{
ContractResolver = new ConfigurableContractResolver
{
}.Configure((s, e) => { e.Contract.AddStringConverters(); }),
};
var candidate = JsonConvert.DeserializeObject<Candidate>(json, settings);
var json2 = JsonConvert.SerializeObject(candidate, Formatting.Indented, settings);
Notes:
I don't know why the stack of containing object(s) is not available in ReadJson(). Possibilities include:
Simplicity.
A JSON object is "an unordered set of name/value pairs", so trying to access the containing .Net object while reading a property value isn't guaranteed to work, since the information required might not have been read in yet (and the parent might not even have been constructed).
Because a default instance of StringSanitizingConverter is applied to the contract generated for string itself, it is not necessary to add the converter to JsonSerializer.SettingsConverters. This in turn may lead to a small performance enhancement as CanConvert will no longer get called.
JsonProperty.MemberConverter was recently marked obsolete in Json.NET 11.0.1 but must be set to the same value as JsonProperty.Converter in previous versions of Json.NET. If you are using 11.0.1 or a more recent version you should be able to remove the setting.
You may want to cache the contract resolver for best performance.
To modify JsonSerializerSettings in asp.net-web-api, see JsonSerializerSettings and Asp.Net Core, Web API: Configure JSON serializer settings on action or controller level, How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API? or ASP.NET Core API JSON serializersettings per request, depending on your requirements and the version of the framework in use.
Sample working .Net fiddle here.
Most of my api routes are segmented like so:
/api/{segment}/MyEntity (i.e. "/api/SegmentA/MyEntity")
Where I've defined a ModelBinder that converts from the string to a Segment object like so:
class SegmentModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (value == null || String.IsNullOrEmpty(value.AttemptedValue))
return false;
bindingContext.Model = **logic to find segment object from value.AttemptedValue**;
return true;
}
}
Configured as:
GlobalConfiguration.Configuration.BindParameter(typeof(Segment), new SegmentModelBinder());
So my routes end up looking like this:
public class MyEntityController : BaseController
{
[HttpGet, Route("api/{segment}/MyEntity")]
public IEnumerable<MyEntity> Get(Segment segment)
{
...
}
}
The problem is, I'm now attempting to generate documentation for these Api calls, and ApiExplorer is completely confused by these routes and ignores them.
How do I tell it that for these routes, when it sees a parameter of type Segment, it's really just a string from the route?
Switching from using a ModelBinder to TypeConverter resolved the problem:
[TypeConverter(typeof(MyEntityConverter))]
public class MyEntity
{....
-
public class MyEntityConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
var key = value as string;
if (!String.IsNullOrEmpty(key))
return **Find Entity**;
return base.ConvertFrom(context, culture, value);
}
}
Edit:
If you ever return this entity in call, you need this in there as well, otherwise the newtonsoft json serializer will serialize the class to the type name:
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return false;
}
i'm trying to get a value depending two values, in my listbox i'm trying to do somthing like this :
<TextBlock x:Name="Distance" Text="{Binding lattitude,Longtitude,Converter={StaticResource Distanceconverter}}" />
so, actually the problem that i need to call my converter but depending in 2 values,
any ideas please?
Yeah, change to what you are binding to the following:
<TextBlock x:Name="Distance" Text="{Binding Path=.,Converter={StaticResource Distanceconverter}}" />
And change your DistanceConverter to accept the object which contains both the latitude and longitude. Multi binding is not currently supported in Windows Phone.
At the top of your page, add:
<phone:PhoneApplicationPage.Resources>
<converters:Distanceconverter x:Key="Distanceconverter" />
</phone:PhoneApplicationPage.Resources>
Assuming your binding model looks like:
public class LocationModel
{
public double Longitude { get; set; }
public double Latitude { get; set; }
}
Create a converter in the form of
public class DistanceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var location = value as LocationModel;
if (location != null)
{
// Your business logic here, e.g.
return location.Latitude + location.Latitude;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}