OData en Strange Converter – deel 3

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. :)

Een reactie op OData en Strange Converter – deel 3

  1.  

    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

leave your comment

Zoals de meeste websites gebruiken we cookies om een meer persoonlijke en snelle service te bieden.

Wij gebruiken cookies zodat onze website meer efficiënt kan functioneren, om de prestaties te verbeteren en, eventueel, om op maat reclame van onze partners aan te bieden. Als u doorgaat gaan we ervan uit dat u akkoord gaat alle cookies te krijgen van onze website.

To accept cookies please Click To Continue