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