OData webservice koppelen aan je Windows Phone 7 app en vanuit de app data opslaan in de cloud? Dat is precies wat ik ga bloggen hier als vervolg op het opzetten van de OData webservice en het koppelen van deze OData webservice aan je Windows Phone 7 app.
In de vorige delen heb ik het gehad over het opzetten van de OData webservice en het koppelen van deze webservice aan je Windows Phone 7 app om je data te downloaden en af te beelden in de app. Hier ga ik behandelen hoe we de data ook kunnen opslaan in de webservice vanuit de app. Voorkennis wordt besproken in Deel 2, dus als je die nog niet gelezen hebt, dan zou ik daar wel beginnen.
De webservice
Het versturen van data naar de webservice moet – binnen de Windows Phone 7 app – asynchroon gebeuren. Dit houdt in dat we de data versturen en niet actief wachten tot het klaar is, maar reageren op de event dat het klaar is. Dit zorgt ervoor dat we gewoon andere dingen kunnen doen terwijl de data verzonden wordt.
Het versturen lijkt erg op het ontvangen van de data. In deze blog ga ik het toevoegen van nieuwe data behandelen. Om door te gaan op het voorbeeld van de Strange Converter, moet ik een extra feature toevoegen aan de app, namelijk het kunnen submitten van een nieuwe conversie. Dit kan handig zijn als de Strange Converter gebruikers zelf leuke nieuwe conversies verzinnen en deze in het systeem willen hebben.
Om dit mogelijk te maken moet ik mijn webservice aanpassen, zodat hij accepteerd dat er ook data verzonden kan worden vanuit de app. Ik ga in deze blog niet in op het beveiligen van de webservice, dat komt nog.
In de webservice die we hebben aangemaakt in Deel 1, heb ik juist deze mogelijkheid uitgezet. Dit kan ik makkelijk aanzetten de rechten aan te passen van ReadAll naar All. Hiermee zorg ik dat ik alles toesta op de webservice, het lezen, schrijven, updaten en verwijderen. Dit doe ik in de StrangeConverterDataService.svc.
public class StrangeConverterDataService : DataService<StrangeConverterEntities> { // This method is called only once to initialize service-wide policies. public static void InitializeService(DataServiceConfiguration config) { config.SetEntitySetAccessRule("*", EntitySetRights.All); // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; } }
Hier zijn in regel 6 de rechten veranderd naar EntitySetRights.All. Nu hoef ik alleen mijn webservice to committen naar Bitbucket en hij deployt dan automatisch naar AppHarbor (zie ook Mist in WP7-land)
De app
De volgende stap is de feature en code toevoegen in de app. Hiervoor maak ik een nieuwe PivotItem aan met de invoervelden en knoppen om een suggestie te versturen.
<controls:PivotItem Header="suggestion"> <StackPanel Orientation="Vertical"> <TextBlock TextWrapping="Wrap" Style="{StaticResource PhoneTextNormalStyle}" FontSize="24" Foreground="Black"> Did you uncover new conversions to share? </TextBlock> <TextBlock Text="Convert what?" Margin="12,10,0,0" TextWrapping="Wrap" Style="{StaticResource PhoneTextNormalStyle}" FontSize="24" Foreground="Black" /> <TextBox Name="txtWhat" Margin="0,-10,0,0" BorderBrush="Black" Foreground="Black" InputScope="Text" /> <TextBlock Text="Convert how many?" Margin="12,-10,0,0" TextWrapping="Wrap" Style="{StaticResource PhoneTextNormalStyle}" FontSize="24" Foreground="Black" /> <TextBox Name="txtWhatValue" Margin="0,-10,0,0" BorderBrush="Black" Foreground="Black" InputScope="Number" /> <TextBlock Text="To what?" Margin="12,-10,0,0" TextWrapping="Wrap" Style="{StaticResource PhoneTextNormalStyle}" FontSize="24" Foreground="Black" /> <TextBox Name="txtToWhat" Margin="0,-10,0,0" BorderBrush="Black" Foreground="Black" InputScope="Text" /> <TextBlock Text="To how many?" Margin="12,-10,0,0" TextWrapping="Wrap" Style="{StaticResource PhoneTextNormalStyle}" FontSize="24" Foreground="Black" /> <TextBox Name="txtToWhatValue" Margin="0,-10,0,0" BorderBrush="Black" Foreground="Black" InputScope="Number" /> <Button Content="Submit to the world" Foreground="Black" BorderBrush="Black" Margin="0,0,0,0" Name="ButtonSumbit" Tap="ButtonSumbit_Tap" /> </StackPanel> </controls:PivotItem>
De nieuwe PivotItem bevat wat tekst, vier TextBox-en en een Button om de data te submitten. De button heeft een Tap event die in de CodeBeside wordt afgehandeld.
Om de nieuwe conversion te versturen moet ik de event van de knop behandelen en code toevoegen om het daadwerkelijk te submitten naar de webservice. Die laatste code stop ik in de ViewModel waar hij thuis hoort.
In de event stop ik de volgende code om de data te versturen naar de webservice.
private void ButtonSumbit_Tap(object sender, GestureEventArgs e) { // base unit string baseUnitName = this.txtWhat.Text.ToLower(); // we need this to be lowercase double unit1Value = double.Parse(this.txtWhatValue.Text); // target unit string targetUnitName = this.txtToWhat.Text.ToLower(); // we need this to be lowercase double unit2Value = double.Parse(this.txtToWhatValue.Text); App.ConversionsViewModel.SubmitNewConversion(baseUnitName, unit1Value, targetUnitName, unit2Value); }
Op regel 11 roep ik de ViewModel aan waar ik een nieuwe methode SubmitNewConversion heb gemaakt.
StrangeConverterEntities _Context; // Constructor public ConversionsViewModel() { // create a new instance this.Items = new ObservableCollection<ConversionModel>(); // initialize the context _Context = new StrangeConverterEntities(new Uri("http://strangeconverterwebservice.apphb.com/StrangeConverterDataService.svc/", UriKind.Absolute)); } // create some events to control the async process private delegate void UnitSavedEvent(object sender, EventArgs e); private event UnitSavedEvent Unit1SavedEventHandler; private event UnitSavedEvent Unit2SavedEventHandler; /// <summary> /// Start the submittion process /// </summary> /// <param name="baseUnitName"></param> /// <param name="baseValue"></param> /// <param name="targetUnitName"></param> /// <param name="targetValue"></param> public void SubmitNewConversion(string baseUnitName, double baseValue, string targetUnitName, double targetValue) { // make sure we know that we're busy doing things this.IsDataLoaded = false; // first we create the unit objects // base unit Units baseUnit = new DataService.Units(); baseUnit.Name = baseUnitName; // target unit Units targetUnit = new DataService.Units(); targetUnit.Name = targetUnitName; // define the events for callback // this is the first event that's being called this.Unit1SavedEventHandler += new UnitSavedEvent((sender, e) => { // begin saving the target unit SaveUnit(targetUnit, Unit2SavedEventHandler); }); // this is the second event that's being called this.Unit2SavedEventHandler += new UnitSavedEvent((sender, e) => { // all units are saved, so we can save the conversion this.SaveConversion(baseUnitName, baseValue, targetUnitName, targetValue); }); // begin saving the base unit SaveUnit(baseUnit, Unit1SavedEventHandler); } /// <summary> /// saving the unit /// </summary> /// <param name="unit"></param> /// <param name="eventCallBack"></param> private void SaveUnit(DataService.Units unit, UnitSavedEvent eventCallBack) { // check if the unit already exists DataServiceQuery<DataService.Units> query = (DataServiceQuery<DataService.Units>)(from u in _Context.Units where u.Name == unit.Name select u); // get all units in a collection DataServiceCollection<DataService.Units> unitsCollection = new DataServiceCollection<DataService.Units>(_Context); // initialize the callback unitsCollection.LoadCompleted += new EventHandler<LoadCompletedEventArgs>((sender, e) => { // when no units exist with this name if (unitsCollection.Count == 0) { // save the unit _Context.AddToUnits(unit); _Context.BeginSaveChanges(new AsyncCallback((result) => { // when saving the units is completed _Context.EndSaveChanges(result); // call the eventhandler eventCallBack(unit, new EventArgs()); }), _Context); } // else the unit exists so we don't need to add it again }); // run the query unitsCollection.LoadAsync(query); } /// <summary> /// method for saving the conversion /// </summary> /// <param name="baseUnitName"></param> /// <param name="targetUnitName"></param> private void SaveConversion(string baseUnitName, double baseValue, string targetUnitName, double targetValue) { // find the units DataServiceQuery<Units> query = (DataServiceQuery<Units>)(from u in _Context.Units where u.Name == baseUnitName || u.Name == targetUnitName select u); // get all units DataServiceCollection<Units> unitsCollection = new DataServiceCollection<Units>(_Context); // initialize the callback unitsCollection.LoadCompleted += new EventHandler<LoadCompletedEventArgs>((sender, e) => { // load completed // check if we've got the to units if (unitsCollection.Count == 2) { // create the units Units baseUnit = null; Units targetUnit = null; // itterate throught the collection retreived from the webservice foreach (Units u in unitsCollection) { // get the units if (u.Name == baseUnitName) baseUnit = u; if (u.Name == targetUnitName) targetUnit = u; } // check units if both are set if (baseUnit != null && targetUnit != null) { // create the new conversion _Context.AddToConversions(new Conversions() { baseunitid = baseUnit.id, targetunitid = targetUnit.id, basevalue = (decimal)baseValue, targetvalue = (decimal)targetValue }); // begin saving the changes _Context.BeginSaveChanges(new AsyncCallback((result) => { // finish saving _Context.EndSaveChanges(result); // the LoadData() will also set the IsDataLoaded to false when starting and to true when done this.IsDataLoaded = true; }), _Context); } else { // something went wrong this.IsDataLoaded = true; } } else { // something went wrong... } }); unitsCollection.LoadAsync(query); }
Deze code maakt de verschillende objecten aan, stuurt deze asynchroon naar de webservice en wacht op antwoord via een event. Nadat alle Units zijn aangemaakt, wordt de Conversion aangemaakt en op dezelfde manier verstuurd naar de server.
Tot slot
De bovenstaande code verstuurd data naar de server. Deze wordt dan opgeslagen en daarna kan de data opnieuw worden geladen om de app te bevoorraden met de nieuwe gegevens. Belangrijk is wel om te onthouden dat de service nu niet beveiligd is. Ik heb daarom ook deze optie weer uit gezet totdat ik een beveiligde service heb. Dit ga ik volgende keer be-bloggen.
Nog een mooie blogpost op MSDN als je een WCF Data Service wilt gebruiken icm met Entity Framework Code First
http://blogs.msdn.com/b/writingdata_services/archive/2011/10/26/10175174.aspx