Dailycode.info

Short solution for short problems

.Net Selftracking entities loose context after serialization.

You can encounter lots of weird problems when you are working with self tracking entities that loose their context. Certainly when you try to save. Insert, delete and selects will not really depend on their context. But saving can give you problems:

For example, when you send the entity over WCF and change something on a non .Net client. Sometimes he will just save it. But sometimes you will get the error that an object with that key is already in the context. Or a unique key constraint or whatever. I tried several things to solve it. Many tries where good, but none was stable and perfect. 

Now I find I've found a trustworthy and stable solution. I got to it when testing al kinds of automapper implementations. The goal was to map the entity's properties that was decoupled from the context with the entity that was in the context. But very soon I noticed that most automapper just create a new instance of the object and map the properties to this. So that was no help. What I needed was an automapper that would map the properties of one instance to the properties of another instance (the one form the context) and not a new instance with comined properties. So I found mapper code that did the trick here. I'm using the second implementation of the cached property maps. Not using the static implementation, but rather a Singleton. So I can put the initial creation of all mappings in the constructor. To map the object coming from the webservices to the object from the context is simple:

//Get the object from the context

GalvaSFIMobileEntities.GP_MES_F06ByOrder fc = PL.GetF06OrderInfo(f06.SalesOrder);

//Map the object's properties to the properties of the context object

GenericSingleton<SelfTrackingObjectMapper>.GetInstance().CopyMatchingCachedProperties(f06, fc);

//Save the object from the context with the matched proprties.

PL.SaveF06ByOrder(fc);

Now for the implementation of this mapper. Just add a class to your project and past this code:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Reflection;

using System.Text;

using Microsoft.CSharp;

 

public class PropertyMap

{

 

    public PropertyInfo SourceProperty { get; set; }

 

    public PropertyInfo TargetProperty { get; set; }

 

}

 

 

///<summary>

/// Summary description for SelfTrackingObjectMapper

///</summary>

public class SelfTrackingObjectMapper

{

    public SelfTrackingObjectMapper()

    {

        //

        // TODO: Add constructor logic here

        //

        AddPropertyMap<GalvaSFIMobileEntities.GP_MES_F06ByOrder, GalvaSFIMobileEntities.GP_MES_F06ByOrder>();

        AddPropertyMap<GalvaSFIMobileEntities.GP_MES_F06ByOrderDetails, GalvaSFIMobileEntities.GP_MES_F06ByOrderDetails>();

    }

 

    public IList<PropertyMap> GetMatchingProperties(Type sourceType, Type targetType)

    {

        var sourceProperties = sourceType.GetProperties();

        var targetProperties = targetType.GetProperties();

 

        var properties = (from s in sourceProperties

                          from t in targetProperties

                          where s.Name == t.Name &&

                                s.CanRead &&

                                t.CanWrite &&

                                s.PropertyType.IsPublic &&

                                t.PropertyType.IsPublic &&

                                s.PropertyType == t.PropertyType &&

                                (

                                  (s.PropertyType.IsValueType &&

                                   t.PropertyType.IsValueType

                                  ) ||

                                  (s.PropertyType == typeof(string) &&

                                   t.PropertyType == typeof(string)

                                  )

                                )

                          select new PropertyMap

                                     {

                                         SourceProperty = s,

                                         TargetProperty = t

                                     }).ToList();

        return properties;

    }

 

 

    private Dictionary<string, PropertyMap[]> _maps =

    new Dictionary<string, PropertyMap[]>();

 

 

    public void AddPropertyMap<T, TU>()

    {

        var props = GetMatchingProperties(typeof(T), typeof(TU));

        var className = GetClassName(typeof(T), typeof(TU));

        _maps.Add(className, props.ToArray());

    }

 

 

 

    public void CopyMatchingCachedProperties(object source, object target)

    {

        var className = GetClassName(source.GetType(),target.GetType());

        var propMap = _maps[className];

 

        for (var i = 0; i < propMap.Length; i++)

        {

            var prop = propMap[i];

            var sourceValue = prop.SourceProperty.GetValue(source, null);

            prop.TargetProperty.SetValue(target, sourceValue, null);

        }

    }

 

    public string GetClassName(Type sourceType,Type targetType)

    {

        var className = "Copy_";

        className += sourceType.FullName.Replace(".", "_");

        className += "_";

        className += targetType.FullName.Replace(".", "_");

        return className;

    }

}

I choose to keep creation of the mappings based on the properties of both objects. I could in my case only use the properties of the frist object, since they should be the same, but the mappings are only created once and maybe in the future I could use this for other purposes. Eg. POCO -> Self tracking entities. 

Last thing you will need is the GenericSingleton class, you can find this on google, but for the sake of completeness:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace GP_Global

{

    public class GenericSingleton<T> where T : class, new()

    {

        private static T instance;

       

        public static T GetInstance()

        {

            lock (typeof(T))

            {

                if (instance == null)

                {

                    instance = new T();

                }

                return instance;

            }

        }

    }

}


iOS : Save file to windows file share.

I had tried several solutions. The first that really worked stable was to use http post to send the data to a php recieving file. The solution can be found here. But that was hard to control and it fails when files get bigger then 2Mb. It was also relative slow. 

So my current solution works fantastic!!! I'm using FTP now to send files from my iOS device to the Windows server. The only thing you need to do on server side is to enable ftp and create and ftp site. I also used basic authentication with an active directory user. 

On iOS site I followed this example from apple themselves and create a ViewController that can handle all your uploading. Based on file path or NData and file name. You can simply call the class like this:

 

self.uploadController.parentView = self.view;

self.uploadController.dataToSend = imageData;

self.uploadController.fileName = @"MyFile.PNG";

[self.uploadController UploadPressed:sender];

 

First we tell which parent view to use. This is to show the progress of the file upload.

Then we provide the NSData which can be read from image or video or any other kind of resource. Here you get an example of a picture that is on an image view:

 

NSData *imageData = UIImagePNGRepresentation(self.imageView.image);

self.uploadController.dataToSend = imageData;

 

And here an example of a video based on the video's url on the device:

 

NSData *videoData;

if(self.videoUrl)

{

      videoData = [NSData dataWithContentsOfURL:self.videoUrl];

}

        

if (videoData)

{

      self.uploadController.dataToSend = videoData;

      ...

 

So thats really it. It will send your files to the url which you had defined in your App using the user and password. Now here's the ViewController that does all the work for you:

The UploadVC.h

//

//  UploadV3

//  TestBARCodeAndSIngnature

//

//  Created by Mark Deraeve on 28/01/13.

//  Copyright (c) 2013. All rights reserved.

//

 

#import <UIKit/UIKit.h>

#include <CFNetwork/CFNetwork.h>

 

@interface UploadVC : UIViewController <NSStreamDelegate>

 

@property (nonatomicassignreadonly ) BOOL              isSending;

@property (nonatomicstrongreadwriteNSOutputStream *  networkStream;

@property (nonatomicstrongreadwriteNSInputStream *   fileStream;

@property (nonatomicassignreadonly ) uint8_t *         buffer;

@property (nonatomicassignreadwritesize_t            bufferOffset;

@property (nonatomicassignreadwritesize_t            bufferLimit;

@property (nonatomicstrongNSData * dataToSend;

@property (nonatomicstrongNSString * fileName;

@property (nonatomicstrongNSString * filePath;

@property (nonatomicstrongUIView * parentView;

 

- (void)UploadPressed:(id)sender;

@end

The UploadVC.m

//

//  UploadV3

//  TestBARCodeAndSIngnature

//

//  Created by Mark Deraeve on 28/01/13.

//  Copyright (c) 2013. All rights reserved.

//

 

#import "UploadVC.h"

#import "NetworkManager.h"

#import "VariableStore.h"

#import "GeneralMESFunctions.h"

 

 

enum {

    kSendBufferSize = 32768

};

 

@interfaceUploadVC ()

 

 

 

@end

 

@implementation UploadVC

{

    uint8_t                     _buffer[kSendBufferSize];

}

 

VariableStore * store;

MBProgressHUD * hud;

 

- (void)sendDidStart

{

    [[NetworkManagersharedInstancedidStartNetworkOperation];

}

 

- (void)updateStatus:(NSString *)statusString

{

    assert(statusString != nil);

}

 

- (void)sendDidStopWithStatus:(NSString *)statusString

{

    if (statusString == nil) {

        statusString = @"Put succeeded";

    }

    [[NetworkManagersharedInstancedidStopNetworkOperation];

}

 

#pragma mark * Core transfer code

 

// This is the code that actually does the networking.

 

// Because buffer is declared as an array, you have to use a custom getter.

// A synthesised getter doesn't compile.

 

- (uint8_t *)buffer

{

    returnself->_buffer;

}

 

- (BOOL)isSending

{

    return (self.networkStream != nil);

}

 

- (void)startSend:(NSString *) filePath

{

    BOOL                    success;

    NSURL *                 url;

    hud = [GeneralMESFunctionsGetMBProgressHUDForView:self.parentView];

    hud.detailsLabelText = @"Uploading file...";

 

    assert(filePath != nil);

    assert([[NSFileManagerdefaultManagerfileExistsAtPath:filePath]);

    assert( [filePath.pathExtensionisEqual:@"png"] || [filePath.pathExtensionisEqual:@"jpg"] );

    

    assert(self.networkStream == nil);      // don't tap send twice in a row!

    assert(self.fileStream == nil);         // ditto

    

    // First get and check the URL.

    

    url = [[NetworkManagersharedInstancesmartURLForString:store.MESFTPPath];

    success = (url != nil);

    

    if (success) {

        // Add the last part of the file name to the end of the URL to form the final

        // URL that we're going to put to.

        

        url = CFBridgingRelease(

                                CFURLCreateCopyAppendingPathComponent(NULL, (__bridge CFURLRef) url, (__bridge CFStringRef) [filePath lastPathComponent], false)

                                );

        success = (url != nil);

    }

    

    // If the URL is bogus, let the user know.  Otherwise kick off the connection.

    

    if ( ! success) {

        //self.txtStatus.text = @"Invalid URL";

    } else {

        

        // Open a stream for the file we're going to send.  We do not open this stream;

        // NSURLConnection will do it for us.

        

        self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];

        assert(self.fileStream != nil);

        

        [self.fileStream open];

        

        // Open a CFFTPStream for the URL.

        

        self.networkStream = CFBridgingRelease(

                                               CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) url)

                                               );

        assert(self.networkStream != nil);

        

        if ([store.MESFTPUser length] != 0) {

            success = [self.networkStream setProperty:store.MESFTPUser forKey:(id)kCFStreamPropertyFTPUserName];

            assert(success);

            success = [self.networkStream setProperty:store.MESFTPPWD forKey:(id)kCFStreamPropertyFTPPassword];

            assert(success);

        }

        

        self.networkStream.delegate = self;

        [self.networkStreamscheduleInRunLoop:[NSRunLoopcurrentRunLoopforMode:NSDefaultRunLoopMode];

        [self.networkStream open];

        

        // Tell the UI we're sending.

        

        [self sendDidStart];

    }

}

 

- (void)startSend:(NSData *) dataToSend fileName:(NSString *) fileName

{

    BOOL                    success;

    NSURL *                 url;

    

    hud = [GeneralMESFunctionsGetMBProgressHUDForView:self.parentView];

    hud.detailsLabelText = @"Uploading file...";

    

    assert(dataToSend != nil);

    assert(self.networkStream == nil);      // don't tap send twice in a row!

    assert(self.fileStream == nil);         // ditto

    

    // First get and check the URL.

    

    url = [[NetworkManagersharedInstancesmartURLForString:store.MESFTPPath];

    success = (url != nil);

    

    if (success) {

        // Add the last part of the file name to the end of the URL to form the final

        // URL that we're going to put to.

        

        url = CFBridgingRelease(

                                CFURLCreateCopyAppendingPathComponent(NULL, (__bridge CFURLRef) url, (__bridge CFStringRef) fileName, false)

                                );

        success = (url != nil);

    }

    

    // If the URL is bogus, let the user know.  Otherwise kick off the connection.

    

    if ( ! success) {

        //self.txtStatus.text = @"Invalid URL";

    } else {

        

        // Open a stream for the file we're going to send.  We do not open this stream;

        // NSURLConnection will do it for us.

        

        self.fileStream = [NSInputStream inputStreamWithData:dataToSend];

        assert(self.fileStream != nil);

        

        [self.fileStream open];

        

        // Open a CFFTPStream for the URL.

        

        self.networkStream = CFBridgingRelease(

                                               CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) url)

                                               );

        assert(self.networkStream != nil);

        

        if ([store.MESFTPUser length] != 0) {

            success = [self.networkStream setProperty:store.MESFTPUser forKey:(id)kCFStreamPropertyFTPUserName];

            assert(success);

            success = [self.networkStream setProperty:store.MESFTPPWD forKey:(id)kCFStreamPropertyFTPPassword];

            assert(success);

        }

        

        self.networkStream.delegate = self;

        [self.networkStreamscheduleInRunLoop:[NSRunLoopcurrentRunLoopforMode:NSDefaultRunLoopMode];

        [self.networkStream open];

        

        // Tell the UI we're sending.

        

        [self sendDidStart];

    }

}

 

- (void)stopSendWithStatus:(NSString *)statusString

{

    if (self.networkStream != nil) {

        [self.networkStreamremoveFromRunLoop:[NSRunLoopcurrentRunLoopforMode:NSDefaultRunLoopMode];

        self.networkStream.delegate = nil;

        [self.networkStream close];

        self.networkStream = nil;

    }

    if (self.fileStream != nil) {

        [self.fileStream close];

        self.fileStream = nil;

    }

    [MBProgressHUDhideHUDForView:self.parentViewanimated:YES];

    [self sendDidStopWithStatus:statusString];

}

 

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode

// An NSStream delegate callback that's called when events happen on our

// network stream.

{

#pragma unused(aStream)

    assert(aStream == self.networkStream);

    

    switch (eventCode) {

        caseNSStreamEventOpenCompleted: {

            [selfupdateStatus:@"Opened connection"];

        } break;

        caseNSStreamEventHasBytesAvailable: {

            assert(NO);     // should never happen for the output stream

        } break;

        caseNSStreamEventHasSpaceAvailable: {

            [self updateStatus:@"Sending"];

            

            // If we don't have any data buffered, go read the next chunk of data.

            

            if (self.bufferOffset == self.bufferLimit) {

                NSInteger   bytesRead;

                

                bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];

                

                if (bytesRead == -1) {

                    [self stopSendWithStatus:@"File read error"];

                } else if (bytesRead == 0) {

                    [self stopSendWithStatus:nil];

                } else {

                    self.bufferOffset = 0;

                    self.bufferLimit  = bytesRead;

                }

            }

            

            // If we're not out of data completely, send the next chunk.

            

            if (self.bufferOffset != self.bufferLimit) {

                NSInteger   bytesWritten;

                bytesWritten = [self.networkStreamwrite:&self.buffer[self.bufferOffsetmaxLength:self.bufferLimit - self.bufferOffset];

                assert(bytesWritten != 0);

                if (bytesWritten == -1) {

                    [self stopSendWithStatus:@"Network write error"];

                } else {

                    self.bufferOffset += bytesWritten;

                }

            }

        } break;

        caseNSStreamEventErrorOccurred: {

            [selfstopSendWithStatus:@"Stream open error"];

        } break;

        caseNSStreamEventEndEncountered: {

            // ignore

        } break;

        default: {

            assert(NO);

        } break;

    }

}

 

- (void)viewDidLoad

{

    [superviewDidLoad];

// Do any additional setup after loading the view, typically from a

}

 

- (void)didReceiveMemoryWarning

{

    [superdidReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

 

- (void)UploadPressed:(id)sender;

{

    store = [VariableStoresharedInstance];

    if (self.dataToSend && self.fileName)

    {

        [selfstartSend:self.dataToSendfileName:self.fileName];

    }

    else

    {

        [self startSend:self.filePath];

    }

}

 

@end

 

As you can see, you're missing some files. I use a simple progress window MBProgressHUD, for better user experience, but you can loose this. You could also use the activity view. Then I get my settings from a class called Variable store. This is the place were I put all my shared variables used throughout the program. So there I get the url, user and password for the file share. This you can also put hardcoded or get is from your own singleton class. First test are very good. Tested with large video files and pictures.  The last file you will need is the NetWorkManager written by apple:

NetworkManager.h:

 

/*

 File:       NetworkManager.h

 

 Contains:   Shared state and utilities for networking.

 

 Written by: DTS

 

 Copyright:  Copyright (c) 2009-2012 Apple Inc. All Rights Reserved.

 

 */

 

#import <Foundation/Foundation.h>

 

@interface NetworkManager : NSObject

 

+ (NetworkManager *)sharedInstance;

 

- (NSURL *)smartURLForString:(NSString *)str;

- (BOOL)isImageURL:(NSURL *)url;

 

@property (nonatomicassignreadonly ) NSUInteger     networkOperationCount;  // observable

 

- (void)didStartNetworkOperation;

- (void)didStopNetworkOperation;

 

@end

 
NetworkManager.m
 

/*

 File:       NetworkManager.m

 

 Contains:   Shared state and utilities for networking.

 

 Written by: DTS

 

 Copyright:  Copyright (c) 2009-2012 Apple Inc. All Rights Reserved.

 

 */

 

#import "NetworkManager.h"

 

@interface NetworkManager ()

 

// read/write redeclaration of public read-only property

 

@property (nonatomicassignreadwriteNSUInteger     networkOperationCount;

 

@end

 

@implementation NetworkManager

 

@synthesize networkOperationCount = _networkOperationCount;

 

+ (NetworkManager *)sharedInstance

{

    static dispatch_once_t  onceToken;

    static NetworkManager * sSharedInstance;

    

    dispatch_once(&onceToken, ^{

        sSharedInstance = [[NetworkManager allocinit];

    });

    return sSharedInstance;

}

 

- (NSURL *)smartURLForString:(NSString *)str

{

    NSURL *     result;

    NSString *  trimmedStr;

    NSRange     schemeMarkerRange;

    NSString *  scheme;

    

    assert(str != nil);

    

    result = nil;

    

    trimmedStr = [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

    if ( (trimmedStr != nil) && ([trimmedStr length] != 0) ) {

        schemeMarkerRange = [trimmedStr rangeOfString:@"://"];

        

        if (schemeMarkerRange.location == NSNotFound) {

            result = [NSURL URLWithString:[NSString stringWithFormat:@"ftp://%@", trimmedStr]];

        } else {

            scheme = [trimmedStr substringWithRange:NSMakeRange(0, schemeMarkerRange.location)];

            assert(scheme != nil);

            

            if ( ([scheme compare:@"ftp"  options:NSCaseInsensitiveSearch] == NSOrderedSame) ) {

                result = [NSURL URLWithString:trimmedStr];

            } else {

                // It looks like this is some unsupported URL scheme.

            }

        }

    }

    

    return result;

}

 

- (BOOL)isImageURL:(NSURL *)url

{

    BOOL        result;

    NSString *  extension;

    

    assert(url != nil);

    

    extension = [url pathExtension];

    result = NO;

    if (extension != nil) {

        result = ([extension caseInsensitiveCompare:@"gif"] == NSOrderedSame)

        || ([extension caseInsensitiveCompare:@"png"] == NSOrderedSame)

        || ([extension caseInsensitiveCompare:@"jpg"] == NSOrderedSame);

    }

    return result;

}

 

- (void)didStartNetworkOperation

{

    // If you start a network operation off the main thread, you'll have to update this code

    // to ensure that any observers of this property are thread safe.

    assert([NSThread isMainThread]);

    self.networkOperationCount += 1;

}

 

- (void)didStopNetworkOperation

{

    // If you stop a network operation off the main thread, you'll have to update this code

    // to ensure that any observers of this property are thread safe.

    assert([NSThread isMainThread]);

    assert(self.networkOperationCount > 0);

    self.networkOperationCount -= 1;

}

 

@end

 
I removed some code from this file that was related to their demo project and not nice for general purpose use.

iOS: SUDZC sort SOAPArray with sortUsingDescriptors

The generated code from SUDZC lacks the function available from NSMutableArray: sortUsingDescriptors.

So I added this to the SOapArray class.

In SoapArray.h add:

- (void)sortUsingDescriptors:(NSArray *) sortDescriptors;

In SoapArray.m add:

-(void)sortUsingDescriptors:(NSArray *)sortDescriptors

{

    return [self.items sortUsingDescriptors:sortDescriptors];

}


iOS - SOAP - SUDZC - Headers (implement security and passing variables via headers)

When you are working with a WCF back end service, you will find development a really joy. We have a website calling the business layer below the WCF services. Thats working great. The instancing of this layer is done per session. So every ASP.Net session gets an instance of this layer, allowing the session to set variables once and reuse them during the session. For example, loggedin user etc...

Now I have a iOS device that wants to consume this service. To do so I created WCF services and transfer the data via SOAP protocol. (I know not JSON, but for some reasons I choose SOAP). 

Now this all is working fine. Until I started testing with more then 1 client. I noticed that the WCF services only could support PerCall or Singleton. PerSession was just not working as I expected. It was working as perCall. So each time an instance was taken. The business layer was used as singleton and reused by all calls. So I had to find a way to identify each call. From which device it was coming, what user made the call and some more parameters. Even a option to implement security. 

The way to solve this was using SOAP Headers. There you can put variable values and read them on the server. SUDZC already generates the code for you, now its up to you to implement it. I will start with iOS implementation and then show how to read the headers on a .Net server.

We will pass the device name in the header. This is a really simple implementation. Just go to the init of your service and add a new dictionary to the header. 

 

- (id) init

{

    if(self = [super init])

    {

        ...

        

        self.namespace = @"http://tempuri.org/";

        

        self.headers = [NSMutableDictionary dictionaryWithObject:[NSMutableDictionary dictionaryWithObject:[UIDevice currentDevice].name forKey:@"SecurityToken"] forKey:@"Authentication"];

        

        ...

        self.logging = NO;

    }

    return self;

}

So thats it really. Here you can see the generated SOAP message with the header.

<?xmlversion="1.0"encoding="utf-8"?>

<soap:Envelopexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema"

               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"xmlns="http://tempuri.org/"

               xmlns:arr="http://schemas.microsoft.com/2003/10/Serialization/Arrays" >

  <soap:Header>

    <Authentication>

      <SecurityToken>iPad mderaeve</SecurityToken>

    </Authentication>

  </soap:Header>

  <soap:Body>

    <GetShiftPlanForDate>

      <date>2013-01-25T00:00:00.000</date>

      <shift>1</shift>

    </GetShiftPlanForDate>

  </soap:Body>

</soap:Envelope>

Thats all there is on iOS side, now we need to read this on our .Net server and handle it.

I made a small function that you can call in each WCF function:

public void GetMESHeaders(OperationContext operationContext)

    {

        string token = null;

 

        // Look at headers on incoming message.

        for (int i = 0; i < OperationContext.Current.IncomingMessageHeaders.Count; i++)

        {

            MessageHeaderInfo h = OperationContext.Current.IncomingMessageHeaders[i];

 

            // For any reference parameters with the correct name.

            if (h.Name == "Authentication")

            {

                // Read the value of that header.

                XmlReader xr = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(i);

                xr.ReadToDescendant("SecurityToken");

                //Set the device name on the Process layer.

                PL.DeviceName = xr.ReadElementContentAsString();

                //Check if the object is allowed

            }

        }

    }

 And thats all, really easy, still it can take some time to find this out. This way you can really customize the exchange of data and control it very good.


iOS: SUDZC serialize NSArrays used as parameter

When you're working with SUDZC serialization for iOS, you'll notice that the creator stated serialization for NSArrays is not supported yet. My guess is development is stopped, so I'm not expecting it in the near future. Somewhere in the SOAP.m file you'll find this piece of code generated:

// If it s an array, then serialize it as an array.

if([Soap isArray: object]) {

//# NOT IMPLEMENTED

}

That's not gonna help you. In my case I needed a fast and simple solution. So I decided for now to only pass array's containing strings. Then still you'll need to serialize it correct. That involves some extra code in the SOAP.m file. I added some code to the serialize withname method in the SOAP.m:

// Serializes an object to a string, XML representation with a specific node name.

+ (NSString*) serialize: (id) object withName: (NSString*) nodeName {

 

    if([object respondsToSelector:@selector(serialize:)])

    {

        return [object serialize: nodeName];

}

    else

    {

        if([object isKindOfClass:[NSArray class]])

        {

            

            NSMutableString* s = [NSMutableStringstring];

            

            bool setFirstNode=NO;

            for(id obj in object)

            {

                if([obj respondsToSelector:@selector(serializeForArray)])

                {

                    if (!setFirstNode)

                    {

                        [s appendFormat:@"<tem:%@>",nodeName];

                        setFirstNode=YES;

                    }

                    NSMutableString * str = [obj serializeForArray:nil];

                    [s appendFormat:@"%@",str];

                }

                else

                {

                    if (!setFirstNode)

                    {

                        [s appendFormat:@"<%@>",nodeName];

                        setFirstNode=YES;

                    }

                    [s appendFormat:@"<%@>%@</%@>",@"arr:string",obj,@"arr:string"];

                }

            }

            if (setFirstNode)

            {

                [s appendFormat:@"</tem:%@>",nodeName];

            }

            else

            {

                [s appendFormat:@"</%@>",nodeName];

            }

            return s;

            

        }

    }

    return [NSString stringWithFormat:@"<%@>%@</%@>", nodeName, [Soap serialize: object], nodeName];

}

 

Keep in mind that also arrays of objects will pass in this method. So for arrays of objects, I check if the object itself responds to the method serializeForArray. If so, I will serialize it using this method. So make sure objects that will be used for saving or inserting, will need to implement this method. If you do not do this, SUDZC generated code will not work because the objects are not correctly serialized with their prefixes. Here you can see an example of this method:

- (NSMutableString*) serializeForArray: (NSString*) nodeName

{

    NSMutableString* s = [NSMutableStringstring];

    [s appendString:@"<a:OrderDetail>"];

    [s appendString: [selfserializeElements]];

    [s appendString:@"</a:OrderDetail>"];

    return s;

}

If you do not use this serialization for array then the object will get the ten: prefix, but thats not what the SOAP function expects. Again, SOAPUI is great to check the correct SOAP signatures.

As you can see if the object doesn't respond to serializeForArray, for now it will serialize all objects as string. But you can start extending this function, check in the class type and serialize it as what kind of object you like!

And yes, now it works fine!


iOS: Programatically select a row in a tableview

Very simple thing, very simple code. Just putting it here for future use. Mind you that when you preform the selectRowAtIndexPath method, the didSelectRowAtIndexPath is not called. So you will need to trigger this manually.

Here's the code for how to select a row in your UITableView from code:

NSIndexPath * indexPath = [NSIndexPathindexPathForRow:0inSection:0];

[self.tableViewselectRowAtIndexPath:indexPath animated:YESscrollPosition:UITableViewScrollPositionNone];


iOS and SUDZC: using objects from multiple (different) namespaces.

Tackled another problem using SUDZC and found a generic solution for this. 

I was passing objects that were in a different namespace then my function was. By default the prefix looked like this: <a:MyObject/>. But then the a prefix was pointing to another namespace. So now I had to make sure that I could tell my objects which namespace to use. I added a property xmlPrefix to the objects that were used in a different namespace. By default it has @"a" in it, but when i pass these objects in another namespace, I change it to @"b". Then I add this extra namespace in the SOAP request and it works. It took a while to find, but now this problem will not hold me back any more in the future.

The prefix look like this:

 

NSString* const SOAP_FUNCTION_SYS_PREFIX = @"xmlns:b=\"http://schemas.datacontract.org/2004/07/GalvaSFIMobileEntities.SYS\"";

 

In the object I added the xmlPrefix property:

 

@property (retain, nonatomic) NSString * xmlPrefix;

 

Then I set in by default to @"a" in the super init:

 

- (id) init

{

    if(self = [super init])

    {

        self.BlockCode = nil;

        ...

        self.xmlPrefix=@"a";

        

    }

    returnself;

}

 

Next I change it when the objects are used in soap request with multiple custom namespaces:

 

OrderBlockCodes * code = ((OrderBlockCodes *)[self.orderBlockCodesArrayobjectAtIndex: indexPath.row]);

code.xmlPrefix=@"b";

[self.selectedBlockCodes addObject: code];

 

So finally the XML looks like this:

 <?xml version="1.0" encoding="utf-8"?>

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:tem="http://tempuri.org/" xmlns:ser="http://schemas.microsoft.com/2003/10/Serialization/"  xmlns:b="http://schemas.datacontract.org/2004/07/MobileEntities.SYS" >

<soapenv:Body>

<tem:BlockOrderPosition>

<tem:lOrder>356009200</tem:lOrder>

<tem:sOrderPos>30</tem:sOrderPos>

<tem:bBlock>0</tem:bBlock>

<tem:bFullOrder>0</tem:bFullOrder>

<tem:remark>Jmblkjjtlilu heilig kuub. Iulh</tem:remark>

<tem:codesToBlock><b:OrderBlockCodes><b:BlockCode>A1</b:BlockCode><b:BlockCodeClass1>P</b:BlockCodeClass1><b:BlockCodeDescription>Openings / Gaten </b:BlockCodeDescription><b:BlockCodeGroup>A</b:BlockCodeGroup><ChangeTracker><b:State>Unchanged</b:State></ChangeTracker><b:Created>2009-05-15T00:00:00.000</b:Created><b:CreatedBy>mderaeve</b:CreatedBy><b:ModifiedBy></b:ModifiedBy><b:UpdCount>1</b:UpdCount></b:OrderBlockCodes>...


iOS : passing < (smaller then or less then) as SOAP parameter

I was passing queries from the client to the server to fill up objects. In the queries, sometimes the < sing appeared. when it did, the query string parameter did not reached the server. So I found this solution on stackoverflow. 

Just embed the querystring in CDATA tag like this:

NSString * parsedQuery = [NSString stringWithFormat:@"<![CDATA[\n%@\n]]>",query];

 

So now you can pass a query like this to your web service:


NSString * query = [NSStringstringWithFormat:@"SELECT * FROM PlanOrders AS pl  LEFT OUTER JOIN ERP_Customers AS cu ON pl.Client = cu.Client AND pl.CustomerID = cu.Customer  inner join Orders o on pl.SalesOrder = o.SalesOrder and pl.Client = o.Client and pl.Plant = o.Plant  WHERE pl.Client = '%@' AND pl.Plant = '%@' AND pl.PlanDate IS NOT NULL AND pl.PlanDate >= '%@'  AND pl.PlanDate < dateadd(day,1,'%@') AND (pl.PlanStatus < 100)  ORDER BY pl.PlanDate",store.Client, store.Plant,[formatter stringFromDate:self.dateFrom],[formatter stringFromDate:self.dateTo]];


NSString * parsedQuery = [NSString stringWithFormat:@"<![CDATA[\n%@\n]]>",query];

[service ExecuteQueryForiOS:self action:@selector(getPlanning:) commandText:parsedQuery];


iOS : Show datepicker from UITextfield (Extra Generic function for easy creation below using Categories)

Simple thing to ask and luckily, reasonable simple solution. You can add some code on the Editing Did Begin of your text box:

 

- (IBAction)txtFromClicked:(id)sender

{

    UIViewController* popoverContent = [[UIViewControlleralloc] init]; //ViewController

    

    UIView *popoverView = [[UIView alloc] init];   //view

    popoverView.backgroundColor = [UIColor blackColor];

    

    UIDatePicker *datePicker=[[UIDatePickeralloc]init];//Date picker

    datePicker.frame=CGRectMake(0,44,320, 216);

    datePicker.datePickerMode = UIDatePickerModeDate;

    if (self.txtFrom.text.length>0)

    {

        @try {

            NSDateFormatter * formatter = [[NSDateFormatter alloc]init];

            [formatter setDateFormat:@"dd/MM/yyyy"];

            NSDate * dateToSet = [formatter dateFromString:txt.text];

            datePicker.date = dateToSet;

        }

        @catch (NSException *exception) {

           //if the date is in wrong format, set date to today

           datePicker.date = [NSDate date];

        }

    }

    [datePicker setMinuteInterval:1];

    [datePicker setTag:10];

    [datePicker addTarget:selfaction:@selector(Result:) forControlEvents:UIControlEventValueChanged];

    [popoverView addSubview:datePicker];

    

    popoverContent.view = popoverView;

    self.popoverController = [[UIPopoverControlleralloc] initWithContentViewController:popoverContent];

    self.popoverController.delegate=self;

    

    [self.popoverControllersetPopoverContentSize:CGSizeMake(320, 264) animated:NO];

    [self.popoverControllerpresentPopoverFromRect:self.txtFrom.frameinView:self.viewpermittedArrowDirections:UIPopoverArrowDirectionUpanimated:YES];

}

 

- (void) Result : (id) sender

{

    NSDate * dateSelected = ((UIDatePicker *) sender).date;

    NSDateFormatter * formatter = [[NSDateFormatteralloc]init];

    [formatter setDateFormat:@"dd/MM/yyyy"];

    self.txtFrom.text = [formatter stringFromDate:dateSelected];

}

 
This code does the trick. It will also set the date in the picker if a date was selected before. What it does is simply show the date picker in a popover for the frame of the textfield. The result method has the sender date picker and there you can find the selected date.

To make this code a little more general, because you will use it probably more then once, I made this into a static method in a general class:

+ (UIPopoverController *) CreatePopOverControllerWithDatePickerForTextField: (UITextField *) txt andDelegate: (id) del

{

    UIPopoverController * cont;

    

    UIViewController* popoverContent = [[UIViewControlleralloc] init]; //ViewController

    

    UIView *popoverView = [[UIView alloc] init];   //view

    popoverView.backgroundColor = [UIColor blackColor];

    

    UIDatePicker *datePicker=[[UIDatePickeralloc]init];//Date picker

    datePicker.frame=CGRectMake(0,44,320, 216);

    datePicker.datePickerMode = UIDatePickerModeDate;

    if (txt.text.length>0)

    {

       @try {

            NSDateFormatter * formatter = [[NSDateFormatter alloc]init];

            [formatter setDateFormat:@"dd/MM/yyyy"];

            NSDate * dateToSet = [formatter dateFromString:txt.text];

            datePicker.date = dateToSet;

        }

        @catch (NSException *exception)

        {

            //if the date is in wrong format, set date to today

            datePicker.date = [NSDate date];

        }

    }

    [datePicker setMinuteInterval:1];

    [datePicker setTag:10];

    [datePicker addTarget:del action:@selector(Result:) forControlEvents:UIControlEventValueChanged];

    [popoverView addSubview:datePicker];

    

    popoverContent.view = popoverView;

    cont = [[UIPopoverControlleralloc] initWithContentViewController:popoverContent];

    cont.delegate=del;

    [cont setPopoverContentSize:CGSizeMake(320, 264) animated:NO];

    return cont;

}

Now you can simply create the date picker popover providing the textfield and delegate like this:

@interfacePlanningVC ()

@property (strong, nonatomic) UIPopoverController * popoverController;

@end

 

@implementation PlanningVC

 

@synthesize popoverController;

 

- (IBAction)txtFromClicked:(id)sender

{

    self.popoverController = [GeneralMESFunctionsCreatePopOverControllerWithDatePickerForTextField:self.txtFromandDelegate:self];

    [self.popoverControllerpresentPopoverFromRect:self.txtFrom.frameinView:self.viewpermittedArrowDirections:UIPopoverArrowDirectionUpanimated:YES];

}

 

...

 

So that did the trick, or so I thought. When I tried to use a second text box, I already had a problem in my delegate function, because there I literally assing the value to a specific text box. And in this method, I do not really know from which text box it is coming from. So I solved this in a generic way using categories. I created a category of a date picker and in the method I tell the date picker from which control it is coming from. Then when the Result method is called (Value of the date picker is changed) I can simply ask the date picker from which control its coming from. So first I needed to make a category of UIDatePicker extending the date picker with an extra property if type id. There I store the control:

 

//

//  UIDatePicker+MESUIDatePicker.h

//  GalvaSFIApp

//

//  Created by Mark Deraeve on 10/01/13.

//

 

#import <UIKit/UIKit.h>

 

@interface UIDatePicker (MESUIDatePicker)

@property (retain, nonatomic) id usedField;

@end

 

//

//  UIDatePicker+MESUIDatePicker.m

//  GalvaSFIApp

//

//  Created by Mark Deraeve on 10/01/13.

//

 

#import "UIDatePicker+MESUIDatePicker.h"

#import <objc/runtime.h>

 

static char const * const ObjectTagKey ="ObjectTag";

 

@implementation UIDatePicker (MESUIDatePicker)

@dynamic usedField;

 

- (id) usedField

{

    returnobjc_getAssociatedObject(self, ObjectTagKey);

}

 

- (void)setUsedField:(id) usedField

{

    objc_setAssociatedObject(self, ObjectTagKey, usedField, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

 

@end

 

It looks a bit odd, but because it is a category, we have to use the c library to associate the property to the instance of the object. Using id provides us freedom to store whatever kind of control in there. It doesn't have to be a UITextBox.

 

So now I tuned my generic date picker popover creation method to use this category and add the sender textbox to the date picker:

 

#import "UIDatePicker+MESUIDatePicker.h"

 

@implementation GeneralMESFunctions

 

+ (UIPopoverController *) CreatePopOverControllerWithDatePickerForTextField: (UITextField *) txt andDelegate: (id) del

{

    UIPopoverController * cont;

    

    UIViewController* popoverContent = [[UIViewControlleralloc] init]; //ViewController

    

    UIView *popoverView = [[UIView alloc] init];   //view

    popoverView.backgroundColor = [UIColor blackColor];

    

    UIDatePicker *datePicker=[[UIDatePickeralloc]init];//Date picker

    datePicker.frame=CGRectMake(0,44,320, 216);

    datePicker.datePickerMode = UIDatePickerModeDate;

    datePicker.usedField = txt;

    if (txt.text.length>0)

    {

       @try {

            NSDateFormatter * formatter = [[NSDateFormatter alloc]init];

            [formatter setDateFormat:@"dd/MM/yyyy"];

            NSDate * dateToSet = [formatter dateFromString:txt.text];

            datePicker.date = dateToSet;

        }

        @catch (NSException *exception)

        {

            //if the date is in wrong format, set date to today

            datePicker.date = [NSDate date];

        }

    }

    [datePicker setMinuteInterval:1];

    [datePicker setTag:10];

    [datePicker addTarget:del action:@selector(Result:) forControlEvents:UIControlEventValueChanged];

    [popoverView addSubview:datePicker];

    

    popoverContent.view = popoverView;

    cont = [[UIPopoverControlleralloc] initWithContentViewController:popoverContent];

    cont.delegate=del;

    [cont setPopoverContentSize:CGSizeMake(320, 264) animated:NO];

    return cont;

}

 

Finally I can look which control triggered the EventValueChanged event and fill it up, for "completions" sake, I added the whole implementation:

 

//

//  PlanningVC.m

//  GalvaSFIApp

//

//  Created by Mark Deraeve on 29/11/12.

//

 

#import "PlanningVC.h"

#import "GeneralMESFunctions.h"

#import "UIDatePicker+MESUIDatePicker.h"

 

@interfacePlanningVC ()

@property (strong, nonatomic) UIPopoverController * popoverControllerFrom;

@property (strong, nonatomic) UIPopoverController * popoverControllerTo;

@end

 

@implementation PlanningVC

 

@synthesize popoverControllerFrom;

@synthesize popoverControllerTo;

 

- (IBAction)txtToClicked:(id)sender

{

    self.popoverControllerTo = [GeneralMESFunctionsCreatePopOverControllerWithDatePickerForTextField:self.txtToandDelegate:self];

    [self.popoverControllerTopresentPopoverFromRect:self.txtTo.frameinView:self.viewpermittedArrowDirections:UIPopoverArrowDirectionUpanimated:YES];

}

 

- (IBAction)txtFromClicked:(id)sender

{

    self.popoverControllerFrom = [GeneralMESFunctionsCreatePopOverControllerWithDatePickerForTextField:self.txtFromandDelegate:self];

    [self.popoverControllerFrompresentPopoverFromRect:self.txtFrom.frameinView:self.viewpermittedArrowDirections:UIPopoverArrowDirectionUpanimated:YES];

}

 

- (void) Result : (id) sender

{

    UIDatePicker * datePicker = ((UIDatePicker *) sender);

    NSDate * dateSelected = datePicker.date;

    NSDateFormatter * formatter = [[NSDateFormatteralloc]init];

    [formatter setDateFormat:@"dd/MM/yyyy"];

    UITextField * txt = (UITextField *) datePicker.usedField;

    txt.text = [formatter stringFromDate:dateSelected];

}

 

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

{

    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

    if (self) {

        // Custom initialization

    }

    returnself;

}

 

- (void)viewDidLoad

{

    [superviewDidLoad];

// Do any additional setup after loading the view.

}

 

- (void)didReceiveMemoryWarning

{

    [superdidReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

 

@end

 

You could even use only 1 function to capture the touch down events of both text boxes and pass the sender in stead of the self.txt property.

Here you can see the result:

 

***Edit

If you cannot manage with my example, go and have a look here:

https://github.com/TimCinel/ActionSheetPicker

 

If found this when I was looking for a generic way to show a pickerview in a popup. It works great. I created and extra class that inherits from the AbstractActionSheetPicker. 


iOS : Get the current date without time (eg: 2013/1/7 0:0:0)

I had to look for this a minute or 2. Its not that straight forward as .Net. You will have to use the NSDateComponents to create a date with only the year, month and day from the NSDate date. After serialization, the date looks like this: 2013-01-07T00:00:00.00.

Code:

NSDateComponents * dc = [[NSCalendarcurrentCalendar] components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:[NSDatedate]];

plan.ShiftDate = [[NSCalendarcurrentCalendar] dateFromComponents:dc];