I am having a problem with my Listbox not updating/refresing.I have read here that i need to use ObservableCollection but i didn't have any luck.I am populating my Listbox from a XML.
public class PestotoraPost
{
public string ID { get; set; }
public string Date { get; set; }
public string Name { get; set; }
public string Message { get; set; }
}
void WebLoad()
{
WebClient pestotora = new WebClient();
pestotora.DownloadStringCompleted += new DownloadStringCompletedEventHandler(pestotora_DownloadStringCompleted);
pestotora.DownloadStringAsync(new Uri("wwww.someURL.com/xml.php"));
}
Note that the actual xml is a php containing the xml structure (from a sql DB)
void pestotora_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
XElement doc = XElement.Parse(e.Result);
listBox1.ItemsSource = from results in doc.Descendants("Data")
select new PestotoraPost
{
ID = results.Element("DataID").Value,
Date = results.Element("DataDate").Value,
Name=results.Element("DataName").Value,
Message=results.Element("DataMessage").Value
};
}
Everytime the XML changes,my Listbox won't update doing a listBox1.UpdateLayout();
Any clue/help on how to start implementing this one?
Thank you very much.
UPDATED
namespace pestotora
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
UIload(true);
WebLoad();
}
public ObservableCollection<PestotoraPost> Posts { get; set; }
public class PestotoraPost
{
public string ID { get; set; }
public string Date { get; set; }
public string Name { get; set; }
public string Message { get; set; }
}
void WebLoad()
{
WebClient pestotora = new WebClient();
pestotora.DownloadStringCompleted += new DownloadStringCompletedEventHandler(pestotora_DownloadStringCompleted);
pestotora.DownloadStringAsync(new Uri("www.domain.com/xml.php"));
}
void UIload(bool Splash)
{
if (Splash == true)
{
ApplicationBar.IsVisible = false;
}
else
{
ApplicationBar.IsVisible = true;
}
}
void pestotora_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
//XDocument doc = XDocument.Parse(e.Result);
XElement doc = XElement.Parse(e.Result);
/* listBox1.ItemsSource = from results in doc.Descendants("Data")
select new PestotoraPost
{
ID = results.Element("DataID").Value,
Date = results.Element("DataDate").Value,
Name=results.Element("DataName").Value,
Message=results.Element("DataMessage").Value
}; */
var list = from results in doc.Descendants("Data")
select new PestotoraPost
{
ID = results.Element("DataID").Value,
Date = results.Element("DataDate").Value,
Name = results.Element("DataName").Value,
Message = results.Element("DataMessage").Value
};
Posts = new ObservableCollection<PestotoraPost>(list);
foreach (var post in list)
{
Posts.Add(post);
}
UIload(false);
}
private void ApplicationBarMenuItem_Click(object sender, EventArgs e)
{
//stckPost.Visibility = System.Windows.Visibility.Visible;
}
private void ApplicationBarIconButton_Click(object sender, EventArgs e)
{
NavigationService.Navigate(new Uri("/Post.xaml", UriKind.Relative));
}
private void ApplicationBarIconButton_Click_1(object sender, EventArgs e)
{
listBox1.UpdateLayout();
//listBox1.ScrollIntoView(listBox1.Items[0]);
WebLoad();
}
}
}
1. If you use MVVM pattern: In viewModel implement INotifyPropertyChanged and declare:
private ObservableCollection<PestotoraPost> _posts;
public ObservableCollection<PestotoraPost> Posts
{
get{return _posts;}
set
{
_posts = value;
RaisePropertyChanged("Posts");
}
}
in xaml write:
<ListBox Name="listbox1" ItemsSource="{Binding Posts}"/>
in pestotora_DownloadStringCompleted method:
var list = from results in doc.Descendants("Data")
select new PestotoraPost
{
ID = results.Element("DataID").Value,
Date = results.Element("DataDate").Value,
Name=results.Element("DataName").Value,
Message=results.Element("DataMessage").Value
};
Posts = new ObservableCollection<PestotoraPosts>(list);
2. If you run everything in a code-behind just declare
public ObservableCollection<PestotoraPosts> Posts {get;set;}
And in Page constructor init collection:
Posts = new ObservableCollection<PestotoraPosts>();
Xaml will have the same declaration.
And in pestotora_DownloadStringCompleted method:
var list = from results in doc.Descendants("Data")
select new PestotoraPost
{
ID = results.Element("DataID").Value,
Date = results.Element("DataDate").Value,
Name=results.Element("DataName").Value,
Message=results.Element("DataMessage").Value
};
foreach(var post in list)
{
Posts.Add(post);
}
What have we done?
In xaml we set a databinding . Now listBox1 will get data from Post collection.
Then we created new collection and notyfied listBox1 to update it's view. (in first vatiant).
In second variant we populated collection and as it was observable it notifyed a list box.
Related
I have a carouselview, in that view I have an ObservableCollection binded as an itemssource. I am able to bind the collection and it would show when I execute the viewmodel's command in the OnAppearing event.
Code that works:
Second Page
public partial class SecondPage : ContentPage
{
public Coll(bool hard, string subject)
{
InitializeComponent();
var vm = (DataSelectionViewModel)BindingContext;
vm.Hard = hard;
vm.Subject = subject;
/* had to set "hard" and "subject" here again, otherwise data won't load */
}
protected override async void OnAppearing()
{
var vm = (DataSelectionViewModel)BindingContext;
base.OnAppearing();
await vm.LoadData.ExecuteAsync().ConfigureAwait(false);
}
}
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
private string subject;
public string Subject { get => subject; set => SetProperty(ref subject, value); }
private bool hard;
public bool Hard { get => hard; set => SetProperty(ref hard, value); }
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
foreach (var data in filtereddata)
{
FilteredData.Add(data);
}
}
}
First Page where second page gets Hard and Subject values
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
await Navigation.PushAsync(new SecondPage(vm.Hard, vm.Subject));
}
So I want to change my code so that if I press the button on the first page, data instantly starts to filter and add to the ObservableCollection and when it's finished, then navigate to the second page. However if I try to load it to the BaseViewModel and then get the data from the second viewmodel it won't show the data.
Code that doesn't work:
Second Page
public partial class SecondPage : ContentPage
{
public SecondPage()
{
InitializeComponent();
}
}
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
FilteredData = new ObservableCollection<Items>();
}
}
BaseViewModel
public class BaseViewModel : INotifyPropertyChanged
{
private string subject;
public string Subject { get => subject; set => SetProperty(ref subject, value); }
private bool hard;
public bool Hard { get => hard; set => SetProperty(ref hard, value); }
public ObservableCollection<Items> FilteredData { get; set; }
/* BaseViewModel has implementation of SetProperty */
}
First Page where second page gets Hard and Subject values
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
}
First Page viewmodel
public class FirstPageViewModel : BaseViewModel
{
public IAsyncCommand MehetButtonClickedCommand { get; }
readonly IPageService pageService;
readonly IFeladatokStore _feladatokStore;
public FeladatValasztoViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
ButtonClickedCommand = new AsyncCommand(ButtonClicked);
pageService = DependencyService.Get<IPageService>();
}
private async Task ButtonClicked()
{
await FilterData();
await pageService.PushAsync(new SecondPage());
}
private async Task FilterData()
{
FilteredData.Clear();
var datas = await _dataStore.SearchData(Subject, Hard).ConfigureAwait(false);
foreach (var data in datas)
{
FilteredData.Add(data);
}
}
So basically this gives a null exception error. I also tried giving the ObservableCollection as an argument for SecondPage(ObservableCollection x) and that did work, but because I had to make another ObservableCollection for it and copy from one to another it stopped being async and froze for a couple of seconds. So my question is how can I make this async?
To avoid delay, build the new collection in a private variable. Then set the property to that variable:
// Constructor with parameter
public SomeClass(IList<Items> data)
{
SetFilteredDataCopy(data);
}
public ObservableCollection<Items> FilteredData { get; set; }
private void SetFilteredDataCopy(IList<Items> src)
{
var copy = new ObservableCollection<Items>();
foreach (var item in src)
copy.Add(item);
FilteredData = copy;
//MAYBE OnPropertyChanged(nameof(FilteredData));
}
I have a CalendarioView from this Xamarin.Plugin.Calendar nuget package.
I've been following this tutorial, and I want to have the same result. Instead of assigning the EventCollection list manually, as in the example, I have my List.
How to fill it in the EventCollection? I've searched and didn't find anything that worked.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:c ="clr-namespace:Minha_Carteira_Hospitalar.Controls"
x:Class="Minha_Carteira_Hospitalar.Views.PlanoReceita"
xmlns:controls="clr-namespace:Xamarin.Plugin.Calendar.Controls;assembly=Xamarin.Plugin.Calendar">
<controls:Calendar
Events="{Binding Events}"
>
<controls:Calendar.EventTemplate>
<DataTemplate>
<StackLayout Padding="15,0,0,0">
<Label
Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Medium" />
</StackLayout>
</DataTemplate>
</controls:Calendar.EventTemplate>
</controls:Calendar>
MVVM code
public EventCollection Events;
public ObservableCollection<Plans> myPlans= new ObservableCollection<Plans>();
public ObservableCollection<Plans> MyPlans
{
get => myPlans;
set => myPlans= value;
}
public MyPlansViewModel()
{
Events = new EventCollection();
}
public ICommand LoadingMyPlans
{
get
{
return new Command(async () =>
{
try
{
List<Plans> tmp = await App.Database.GetMyPlans();
foreach(var item in tmp)
{
MyPlans.Clear();
tmp.ForEach(i => MyPlans.Add(i));
Events.Add(item.DatePlan, MyPlans);
}
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Error", ex.Message, "OK");
}
});
}
}
I am not sure where you use the LoadingMyPlans for. I make a simple example about how to fill your own list into EventCollection for your reference.
The same Xaml as yours.
Model:
public class Plans
{
public DateTime dateTime { get; set; }
public List<Plan> plans { get; set; }
}
public class Plan
{
public string Name { get; set; }
public string Desc { get; set; }
}
ViewModel:
public class CalendarViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
{
return false;
}
storage = value;
OnPropertyChanged(propertyName);
return true;
}
public EventCollection Events { get; set; }
public ObservableCollection<Plans> plans { get; set; }
public CalendarViewModel()
{
plans = new ObservableCollection<Plans>()
{
new Plans(){ dateTime=DateTime.Now, plans=new List<Plan>() { new Plan() { Name = "Plan_A", Desc = "aaaaa" }, new Plan() { Name = "Plan_A2", Desc = "aaaaa2" } }},
new Plans(){ dateTime=DateTime.Now.AddDays(5), plans=new List<Plan>() { new Plan() { Name = "Plan_B", Desc = "bbbbb" }, new Plan() { Name = "Plan_B2", Desc = "aaaaa2" } }},
new Plans(){ dateTime=DateTime.Now.AddDays(-3), plans=new List<Plan>() { new Plan() { Name = "Plan_C", Desc = "ccccc" }}}
};
Events = new EventCollection();
foreach (var item in plans)
{
Events.Add(item.dateTime, item.plans);
}
}
}
Code behind:
public Page2()
{
InitializeComponent();
this.BindingContext = new CalendarViewModel();
}
I have a xamarin forms application and would like to save some values that I got from the picker via the web api. The objective is to save this value as well as the other properties in the web api that is linked to the sql server database, but I have issues in how to reference the value selected in the picker through mvvm. I can load the data from the picker but I just don't know how to save these values by referencing the picker in mvvm.
UsuarioModel Class
This is the model class, it has the CodPerfil property which is the foreign key that should be stored in my web api database and must correspond to the value that will be selected in the picker.
public class UsuarioModel
{
public int CodUsuario { get; set; }
public string Nome { get; set; }
public string Senha { get; set; }
public int Telefone { get; set; }
public DateTime DataRegisto { get; set; }
public bool Estado { get; set; }
public int CodPerfil { get; set; }
}
PerfilModel Class
public class PerfilModel
{
public int CodPerfil { get; set; }
public string Titulo { get; set; }
}
Web API Controller to Insert Data
public IHttpActionResult Registo(UsuarioModel usuario)
{
connection();
SqlCommand cmd = new SqlCommand("SpAddNewUser", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#Nome", usuario.Nome);
cmd.Parameters.AddWithValue("#Senha", usuario.Senha);
cmd.Parameters.AddWithValue("#Telefone", usuario.Telefone);
cmd.Parameters.AddWithValue("#CodPerfil", usuario.CodPerfil);
conn.Open();
cmd.ExecuteNonQuery();
return Ok();
}
Web API Controller to Get Data for Picker in Xamarin
public IEnumerable<PerfilModel> GetPerfisApp()
{
List<PerfilModel> perfilModels = new List<PerfilModel>();
connection();
SqlCommand cmd = new SqlCommand("SpGetPerfilApp", conn);
cmd.CommandType = CommandType.StoredProcedure;
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
PerfilModel perfil = new PerfilModel();
perfil.CodPerfil = Convert.ToInt32(reader["CodPerfil"]);
perfil.Titulo = reader["Titulo"].ToString();
perfilModels.Add(perfil);
}
conn.Close();
return perfilModels;
}
ViewModel Class
public class AddRegistoUsuarioViewModel : BaseViewModel
{
ApiServices _apiServices = new ApiServices();
string _nome;
public string Nome
{
get
{
return _nome;
}
set
{
if (value != null)
{
_nome = value;
OnPropertyChanged();
}
}
}
string _senha;
public string Senha
{
get
{
return _senha;
}
set
{
if (value != null)
{
_senha = value;
OnPropertyChanged();
}
}
}
int _telefone;
public int Telefone
{
get
{
return _telefone;
}
set
{
_telefone = value;
OnPropertyChanged();
}
}
int _codperfil;
public int CodPerfil
{
get
{
return _codperfil;
}
set
{
_codperfil = value;
OnPropertyChanged();
}
}
public string Message { get; set; }
public ICommand Registar
{
get
{
return new Command(async () =>
{
var usuario = new UsuarioModel
{
Nome = Nome,
Senha = Senha,
Telefone = Telefone,
CodPerfil = SelectedPerfil.CodPerfil
};
await _apiServices.RegistoUsuarioAsync(usuario);
});
}
}
public AddRegistoUsuarioViewModel()
{
GetPerfisApp();
}
public async void GetPerfisApp()
{
using (var client = new HttpClient())
{
var uri = "https://webapiigarbage-ff4.conveyor.cloud/api/Usuario/PerfisApp";
var result = await client.GetStringAsync(uri);
var PerfilList = JsonConvert.DeserializeObject<List<PerfilModel>>(result);
Perfis = new ObservableCollection<PerfilModel>(PerfilList);
}
}
PerfilModel _selectedPerfil;
public PerfilModel SelectedPerfil
{
get
{
return _selectedPerfil;
}
set
{
if (SelectedPerfil != value)
{
_selectedPerfil = value;
OnPropertyChanged();
}
}
}
ObservableCollection<PerfilModel> _perfis;
public ObservableCollection<PerfilModel> Perfis
{
get
{
return _perfis;
}
set
{
_perfis = value;
OnPropertyChanged();
}
}
}
API Service Class
I tried to use this form: CodPerfil = SelectedPerfil.CodPerfil
But I was not successful.
public async Task RegistoUsuarioAsync(UsuarioModel usuario)
{
var client = new HttpClient();
var json = JsonConvert.SerializeObject(usuario);
HttpContent content = new StringContent(json);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("https://mywebsite/api/Usuario/Registo", content);
}
RegisterPage.xaml.cs
public RegisterPage()
{
InitializeComponent();
BindingContext = new RegistoUsuarioViewModel();
}
RegisterPage.xaml
<Entry Placeholder="Nome de Usuário"
x:Name="NomeEntry" />
<Picker x:Name="PerfilPicker" Title="Selecione o seu Perfil" FontSize="Large" HorizontalOptions="Center"
ItemsSource="{Binding Perfis}"
ItemDisplayBinding="{Binding Titulo}"
SelectedItem="{Binding SelectedPerfil}" />
<Entry Placeholder="Número de Telemóvel"
x:Name="TelefoneEntry"
Keyboard="Telephone"/>
<Entry Placeholder="Senha" x:Name="SenhaEntry" IsPassword="True"/>
<Button Text="Registar"
TextColor="White"
BackgroundColor="#07E3B0"
x:Name="ButtonLogin"
Command="{Binding Registar}"/>
I would be grateful if someone could help me.
thanks for the tips. what happened was that the viewmodel that was being binded in the Register.xaml.cs class was not the one that contained the Register command. I solve the 'problem' by replacing the viewmodel and it worked!
RegisterPage.xaml.cs
public RegisterPage()
{
InitializeComponent();
BindingContext = new AddRegistoUsuarioViewModel();
}
How can I provide control (change) of my dynamic images created in xamarin forms by clicking?
Image ggImage = new Image()
ggImage.Source = otoTip.imagex
ggImage.AutomationId = "seat_" +otoTip.num
ggImage.WidthRequest = imageWidth
ggImage.HeightRequest = 50
ggImage.VerticalOptions = LayoutOptions.Start
ggImage.HorizontalOptions = LayoutOptions.Start
ggImage.GestureRecognizers.Add(tapGestureRecognizer)
For example, I just want to change a resume source.
You have to implement a TapGestureRecognizer and then add it to your image...
Image ggImage = new Image();
ggImage.Source = otoTip.imagex
ggImage.AutomationId = "seat_" +otoTip.num
ggImage.WidthRequest = imageWidth
ggImage.HeightRequest = 50;
ggImage.VerticalOptions = LayoutOptions.Start;
ggImage.HorizontalOptions = LayoutOptions.Start;
// your TapGestureRecognizer implementation, this is just a sample...
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += (s, e) => { ggImage.Source = yourNewSource };
ggImage.GestureRecognizers.Add(tapGestureRecognizer);
In order to change every unique image of your dynamically created images you could extend your OtoTip class and
public class OtoTip : INotifyPropertyChanged
{
public int x { get; set; }
public int y { get; set; }
public int num { get; set; }
public int near { get; set; }
public int status { get; set; }
public int yy { get; set; }
public int xx { get; set; }
// xx
private string _imagex;
public string imagex
{
get { return _imagex; }
set
{
_imagex = value;
OnPropertyChanged(nameof(imagex));
}
}
private string _imageActivex;
public string imageActivex
{
get { return _imageActivex; }
set
{
_imageActivex = value;
OnPropertyChanged(nameof(imageActivex));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
You have to extend your getVoyagesData() now, to also set the imageActivex like your standard image.
Now you have to adjust the TapGestureRecognizer to use the new property
tapGestureRecognizer.Tapped += (s, e) =>
{
if(ggImage.Source = otoTip.imagex) {
ggImage.Source = otoTip.imageActivex;
}
else
{
ggImage.Source = otoTip.imagex;
}
};
Does this help?
Let's say that you have the following code
public class MyClass {
public double Latitude {get; set;}
public double Longitude {get; set;}
}
public class Criteria
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public MyClass MyProp {get; set;}
}
[HttpGet]
public Criteria Get([FromUri] Criteria c)
{
return c;
}
I'd like to know if someone is aware of a library that could transform any object into query string that is understood by a WEB API 2 Controller.
Here is an example of what I'd like
SerializeToQueryString(new Criteria{StartDate=DateTime.Today, EndDate = DateTime.Today.AddDays(1), MyProp = new MyProp{Latitude=1, Longitude=3}});
=> "startDate=2015-10-13&endDate=2015-10-14&myProp.latitude=1&myProp.longitude=3"
A full example with httpClient might look like :
new HttpClient("http://localhost").GetAsync("/tmp?"+SerializeToQueryString(new Criteria{StartDate=DateTime.Today, EndDate = DateTime.Today.AddDays(1), MyProp = new MyProp{Latitude=1, Longitude=3}})).Result;
At the moment, I use a version (taken from a question I do not find again, maybe How do I serialize an object into query-string format? ...).
The problem is that it is not working for anything else than simple properties.
For example, calling ToString on a Date will not give something that is parseable by WEB API 2 controller...
private string SerializeToQueryString<T>(T aObject)
{
var query = HttpUtility.ParseQueryString(string.Empty);
var fields = typeof(T).GetProperties();
foreach (var field in fields)
{
string key = field.Name;
var value = field.GetValue(aObject);
if (value != null)
query[key] = value.ToString();
}
return query.ToString();
}
"Transform any object to a query string" seems to imply there's a standard format for this, and there just isn't. So you would need to pick one or roll your own. JSON seems like the obvious choice due to the availability of great libraries.
Since it seems no one has dealt with the problem before, here is the solution I use in my project :
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Web;
namespace App
{
public class QueryStringSerializer
{
public static string SerializeToQueryString(object aObject)
{
return SerializeToQueryString(aObject, "").ToString();
}
private static NameValueCollection SerializeToQueryString(object aObject, string prefix)
{
//!\ doing this to get back a HttpValueCollection which is an internal class
//we want a HttpValueCollection because toString on this class is what we want in the public method
//cf http://stackoverflow.com/a/17096289/1545567
var query = HttpUtility.ParseQueryString(String.Empty);
var fields = aObject.GetType().GetProperties();
foreach (var field in fields)
{
string key = string.IsNullOrEmpty(prefix) ? field.Name : prefix + "." + field.Name;
var value = field.GetValue(aObject);
if (value != null)
{
var propertyType = GetUnderlyingPropertyType(field.PropertyType);
if (IsSupportedType(propertyType))
{
query.Add(key, ToString(value));
}
else if (value is IEnumerable)
{
var enumerableValue = (IEnumerable) value;
foreach (var enumerableValueElement in enumerableValue)
{
if (IsSupportedType(GetUnderlyingPropertyType(enumerableValueElement.GetType())))
{
query.Add(key, ToString(enumerableValueElement));
}
else
{
//it seems that WEB API 2 Controllers are unable to deserialize collections of complex objects...
throw new Exception("can not use IEnumerable<T> where T is a class because it is not understood server side");
}
}
}
else
{
var subquery = SerializeToQueryString(value, key);
query.Add(subquery);
}
}
}
return query;
}
private static Type GetUnderlyingPropertyType(Type propType)
{
var nullablePropertyType = Nullable.GetUnderlyingType(propType);
return nullablePropertyType ?? propType;
}
private static bool IsSupportedType(Type propertyType)
{
return SUPPORTED_TYPES.Contains(propertyType) || propertyType.IsEnum;
}
private static readonly Type[] SUPPORTED_TYPES = new[]
{
typeof(DateTime),
typeof(string),
typeof(int),
typeof(long),
typeof(float),
typeof(double)
};
private static string ToString(object value)
{
if (value is DateTime)
{
var dateValue = (DateTime) value;
if (dateValue.Hour == 0 && dateValue.Minute == 0 && dateValue.Second == 0)
{
return dateValue.ToString("yyyy-MM-dd");
}
else
{
return dateValue.ToString("yyyy-MM-dd HH:mm:ss");
}
}
else if (value is float)
{
return ((float) value).ToString(CultureInfo.InvariantCulture);
}
else if (value is double)
{
return ((double)value).ToString(CultureInfo.InvariantCulture);
}
else /*int, long, string, ENUM*/
{
return value.ToString();
}
}
}
}
Here is the unit test to demonstrate :
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Framework.WebApi.Core.Tests
{
[TestClass]
public class QueryStringSerializerTest
{
public class EasyObject
{
public string MyString { get; set; }
public int? MyInt { get; set; }
public long? MyLong { get; set; }
public float? MyFloat { get; set; }
public double? MyDouble { get; set; }
}
[TestMethod]
public void TestEasyObject()
{
var queryString = QueryStringSerializer.SerializeToQueryString(new EasyObject(){MyString = "string", MyInt = 1, MyLong = 1L, MyFloat = 1.5F, MyDouble = 1.4});
Assert.IsTrue(queryString.Contains("MyString=string"));
Assert.IsTrue(queryString.Contains("MyInt=1"));
Assert.IsTrue(queryString.Contains("MyLong=1"));
Assert.IsTrue(queryString.Contains("MyFloat=1.5"));
Assert.IsTrue(queryString.Contains("MyDouble=1.4"));
}
[TestMethod]
public void TestEasyObjectNullable()
{
var queryString = QueryStringSerializer.SerializeToQueryString(new EasyObject() { });
Assert.IsTrue(queryString == "");
}
[TestMethod]
public void TestUrlEncoding()
{
var queryString = QueryStringSerializer.SerializeToQueryString(new EasyObject() { MyString = "&=/;+" });
Assert.IsTrue(queryString.Contains("MyString=%26%3d%2f%3b%2b"));
}
public class DateObject
{
public DateTime MyDate { get; set; }
}
[TestMethod]
public void TestDate()
{
var d = DateTime.ParseExact("2010-10-13", "yyyy-MM-dd", CultureInfo.InvariantCulture);
var queryString = QueryStringSerializer.SerializeToQueryString(new DateObject() { MyDate = d });
Assert.IsTrue(queryString.Contains("MyDate=2010-10-13"));
}
[TestMethod]
public void TestDateTime()
{
var d = DateTime.ParseExact("2010-10-13 20:00", "yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
var queryString = QueryStringSerializer.SerializeToQueryString(new DateObject() { MyDate = d });
Assert.IsTrue(queryString.Contains("MyDate=2010-10-13+20%3a00%3a00"));
}
public class InnerComplexObject
{
public double Lat { get; set; }
public double Lon { get; set; }
}
public class ComplexObject
{
public InnerComplexObject Inner { get; set; }
}
[TestMethod]
public void TestComplexObject()
{
var queryString = QueryStringSerializer.SerializeToQueryString(new ComplexObject() { Inner = new InnerComplexObject() {Lat = 50, Lon = 2} });
Assert.IsTrue(queryString.Contains("Inner.Lat=50"));
Assert.IsTrue(queryString.Contains("Inner.Lon=2"));
}
public class EnumerableObject
{
public IEnumerable<int> InnerInts { get; set; }
}
[TestMethod]
public void TestEnumerableObject()
{
var queryString = QueryStringSerializer.SerializeToQueryString(new EnumerableObject() {
InnerInts = new[] { 1,2 }
});
Assert.IsTrue(queryString.Contains("InnerInts=1"));
Assert.IsTrue(queryString.Contains("InnerInts=2"));
}
public class ComplexEnumerableObject
{
public IEnumerable<InnerComplexObject> Inners { get; set; }
}
[TestMethod]
public void TestComplexEnumerableObject()
{
try
{
QueryStringSerializer.SerializeToQueryString(new ComplexEnumerableObject()
{
Inners = new[]
{
new InnerComplexObject() {Lat = 50, Lon = 2},
new InnerComplexObject() {Lat = 51, Lon = 3},
}
});
Assert.Fail("we should refuse something that will not be understand by the server");
}
catch (Exception e)
{
Assert.AreEqual("can not use IEnumerable<T> where T is a class because it is not understood server side", e.Message);
}
}
public enum TheEnum : int
{
One = 1,
Two = 2
}
public class EnumObject
{
public TheEnum? MyEnum { get; set; }
}
[TestMethod]
public void TestEnum()
{
var queryString = QueryStringSerializer.SerializeToQueryString(new EnumObject() { MyEnum = TheEnum.Two});
Assert.IsTrue(queryString.Contains("MyEnum=Two"));
}
}
}
I'd like to thank all the participants even if this is not something that you should usually do in a Q&A format :)