Dailycode.info

Short solution for short problems

IIS copy files to file share in API or web site

Recently, I needed to copy some files from a database to a file share in a .Net web API that runs on an IIS. First I was trying to run the application pool using a domain user, but I couldn't get it working. The application pool keeps stopping, even if the username and password is correct and the local policies are correct.

So I found another better way to do it. In stead of giving the user permissions on the file share. I gave the server permissions on the file share. Then I ran the application pool under the NetworkService and it works.

On the file share under security, put DOMAIN\SERVERNAME and give modify rights (do not forget to include computers in de AD search). Thats it. Now the server can write to this specific location, no harm can be done. In fact, giving a user permissions can be more dangerous, because he can access it anywhere.


.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;

            }

        }

    }

}


Serialized SOAP has some records empty and set to reference another.

Since I have a .Net back end WCF service and a front end iOS consumer, I have tackled several new challenges and learned a lot of what SOAP is all about. The next strange thing I encountered and it took me an hour or 2 to find out the solution was:

On my iOS client I suddenly got 15 records, where 7 of then were just empty records. I could not find the reason, on the server SQL all records were there and all records were filled in and unique. Then I took a closer look to the incoming SOAP message and found out that there were 7 records that looked like this:

<a:GP_MES_SetOrderPosReadyHistory xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" z:Ref="i1"/>

So no contents only an empty xml tag with at the end z:Ref="i1". Soon I discovered that it was referencing a record that was found earlier in the XML with the same keys. So strange, because the rest of the item was different. At this website I found out that settings the IsReference property on the data contract creates this behavior. So one thing you could do is to set it to false. In my case the data contracts were generated by self tracking entities. So I discovered that I had to adjust the key fields of the entities. In my case it was on a view and the key fields were not unique for each records. So I added an extra field to the key fields and now it works perfect.


SUDZC: Saving arrays of objects.

SUDZC works pretty fine out of the box. You will defenately change here and there some things in the generated code. But probably the worst thing most people will end up bumping against is the fact that it doesn't support saving array of objects to the database. Its easy to retrieve arrays of objects, but for some reason the development stopped there and didn't spend any time any more to the sending of SOAPArrays. In my application I had to save objects in 1 statement and process them on the server synchronously. 

This required a deeper implementation and I had to inspect the SOAP envelope that needed to be generated. By default, the SUDZC will generate something like this:

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns: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/"><soap:Body><SaveOrderDetails></SaveOrderDetails></soap:Body></soap:Envelope>

Not much use, there even is no data from the objects passed. The envelope only has the Method call and that's it.

The desired result at the end should look 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:a="http://schemas.datacontract.org/2004/07/SFIMobileEntities" ><soapenv:Body><tem:SaveOrderDetails><tem:f06Details><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T12:33:58.697</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.550</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>1</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>10</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>1</a:Sequence><a:Size>ào</a:Size><a:StoragePlace>à</a:StoragePlace><a:UpdCount>10</a:UpdCount></a:GP_MES_OrderDetails><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T12:33:58.697</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.557</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>2</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>20</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>2</a:Sequence><a:Size>àoo</a:Size><a:StoragePlace>àpp</a:StoragePlace><a:UpdCount>10</a:UpdCount></a:GP_MES_OrderDetails><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T12:33:58.697</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.560</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>3</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>30</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>3</a:Sequence><a:Size>ào</a:Size><a:StoragePlace>à</a:StoragePlace><a:UpdCount>11</a:UpdCount></a:GP_MES_OrderDetails><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T12:33:58.697</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.567</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>4</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>40</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>4</a:Sequence><a:Size>ào</a:Size><a:StoragePlace>à</a:StoragePlace><a:UpdCount>12</a:UpdCount></a:GP_MES_OrderDetails><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T12:33:58.697</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.570</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>5</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>50</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>5</a:Sequence><a:Size>à</a:Size><a:StoragePlace>à</a:StoragePlace><a:UpdCount>8</a:UpdCount></a:GP_MES_OrderDetails><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T14:15:15.517</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.570</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>6</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>60</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>6</a:Sequence><a:Size>io</a:Size><a:StoragePlace>p</a:StoragePlace><a:UpdCount>12</a:UpdCount></a:GP_MES_OrderDetails><a:GP_MES_OrderDetails><a:ChangeTracker><a:State>Unchanged</a:State></a:ChangeTracker><a:Client>900</a:Client><a:Created>2012-12-27T15:07:12.430</a:Created><a:CreatedBy>@CORDOBA</a:CreatedBy><a:Description>u</a:Description><a:Modified>2012-12-28T09:55:07.573</a:Modified><a:ModifiedBy>@CORDOBA</a:ModifiedBy><a:Plant>GX</a:Plant><a:Pos>7</a:Pos><a:ProductCat>t</a:ProductCat><a:Quantity>70</a:Quantity><a:SalesOrder>119000764</a:SalesOrder><a:Sequence>7</a:Sequence><a:Size>tyo</a:Size><a:StoragePlace>ty</a:StoragePlace><a:UpdCount>4</a:UpdCount></a:GP_MES_OrderDetails></tem:f06Details></tem:SaveOrderDetails></soapenv:Body></soapenv:Envelope>

That's something else...

How do we get there. The first thing we will need to do is to add an extra method on our soap class. (soap.h and soap.m)

In the soap.h (add these lines):

//Created by MRD to serialize arrays

+ (NSString*) serializeForArray: (id) object withName: (NSString*) nodeName;

In the soap.m (ass these lines):

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

// if its an array of objects, it will use the serializeForArray implementation on the object array to serialize.

+ (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 (!setFirstNode)

                {

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

                    setFirstNode=YES;

                }

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

                {

                    NSMutableString * str = [obj serializeForArray:nil];

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

                }

                else

                {

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

                }

            }

            if (setFirstNode)

            {

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

            }

            else

            {

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

            }

            return s;

            

        }

    }

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

}

When we have a class that inherits from the SOAPArray, we ill now not call the default serialize method, but the serializeForArray method. This is done by overwriting the SoapArray function call in your SUDZC generated array. The xml that was generated was working only for 1 object. So we needed to add first the parameter name and then the iteration of the objects. In the method below you'll see that first I will add the parameter name with the tem prefix (again inspect your soap call to check of you are using the same namespace. You could do this with SOAPUI by SmartBear.)

You simply add this method (e.g. in the implementation MYArrayOfGP_MES_OrderDetails):

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

{

    NSMutableString *str = [NSMutableStringstring];

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

    [str appendString: [selfserializeAttributes]];

    [str appendString: @">"];

    for (id content in self)

    {

        [str appendString:[Soap serializeForArray:content withName:nodeName]];

    }

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

    return str;

}

 

Now this is where the difference is. We will generate a different SOAP body when the object is used in an array. So the last thing we need to do is to implement this function on the object itself.You could even loose the argument nodeName since its not used in this function. (In my case: implementation GALVGP_MES_OrderDetails)

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

    {

        NSMutableString* s = [NSMutableStringstring];

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

        [s appendString: [self serializeElements]];

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

        return s;

    }

Once you've done this, you can send array of your object to the server and use them there. It works fine. If you still struggle, I could help you with the complete solution, because there are some things I've had to change on the CreateEnvelope for Saving data. The default SUDZC code didn't work for me, since my SOAP required some extra namespaces.

This involved creating a new CreateEnvelope method in the SOAP class. This method should then be called when you do insert or update. This make sure that the namespace for the object prefixes is passed. The method looks like this:

NSString* const SOAP_PREFIX = @"soapenv";

NSString* const NS_PREFIX = @"tem";

NSString* const SER_PREFIX_URL = @"xmlns:ser=\"http://schemas.microsoft.com/2003/10/Serialization/\"";

 

NSString* const HTTP_PREFIX = @"http://";

NSUInteger const FORWARD_FLASH_CHARACTER_VALUE = 47;

// Creates the XML request for the SOAP envelope with optional SOAP headers and extra namespace.

+ (NSString*) createEnvelope: (NSString*) method forNamespace: (NSString*) ns

forParameters: (NSString*) params withHeaders: (NSDictionary*) headers withExtraNameSpace: (NSString *) extraNameSpace

{

    

    NSMutableString* s = [NSMutableStringstring];

 

    [s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];

 

    [s appendString: @"<"];

    [s appendString: SOAP_PREFIX];

    [s appendString:@":Envelope xmlns:"];

    [s appendString: SOAP_PREFIX];

    [s appendString: @"=\"http://schemas.xmlsoap.org/soap/envelope/\" "];

    [s appendString:@" xmlns:"];

    [s appendString: NS_PREFIX];

    [s appendFormat:@"=\"%@\" ",ns ];

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

    if(extraNameSpace)

    {

        [s appendFormat:@" %@ >",extraNameSpace];

    }

    

    if(headers != nil && headers.count > 0) {

        [s appendString: @"<"];

        [s appendString: SOAP_PREFIX];

        [s appendString:@":Header>"];

        

        for(id key in [headers allKeys]) {

            if([[headers objectForKey: key] isMemberOfClass: [SoapNil class]])

            {

                [s appendFormat: @"<%@ xsi:nil=\"true\"/>", key];

            } else {

                [s appendString:[Soap serializeHeader:headers forKey:key]];

            }

        }

        [s appendString: @"</"];

        [s appendString: SOAP_PREFIX];

        [s appendString:@":Header>"];

    }

    

    [s appendString: @"<"];

    [s appendString: SOAP_PREFIX];

    [s appendString:@":Body>"];

    

    NSMutableString* fullMethodName = [NSMutableStringstring];

    [fullMethodName appendString:NS_PREFIX];

    [fullMethodName appendString:@":"];

    [fullMethodName appendString:method];

    

    [s appendFormat: @"<%@>%@</%@>", fullMethodName,[params

                                                     stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"],

     fullMethodName];

    

    

    [s appendString: @"</"];

    [s appendString: SOAP_PREFIX];

    [s appendString:@":Body>"];

    [s appendString: @"</"];

    [s appendString: SOAP_PREFIX];

    [s appendString:@":Envelope>"];

    return s;

}

I used a tool called soapui to inspect the SOAP and find out what the envelope should look like.  


Using nullable/nilable types with iOS

It's obvious that iOS doesn't support nilable or nullable(.Net) types. I started having problems becuase we have a .Net implementation (server side) where we have bool? So on client side it resulted in a YES/NO/N/A choice. In iOS its a Segmented Control that has these options. But when my Entities are converted to iOS objects, they result offcourse in simple types: bool. So this means that its by default no. How to solve this? I could change my objects on server side and regenerate them. But since I'm using the entity framework generation, I do not want to do this. Then my collegue pointed me in the good direction. Using partial classes on server side eventually did the trick. So by extending the base class and adding properties I got a working solution. I will describe it below with some points to pay attention to.

So on iOS site I want int in stead of bool. Int value 0 = YES, 1 = NO and 2 = null. To get this I created some methods on the .Net WCF services. Here is an example of the extension class I used:

public partial class GP_MES_F06ByOrder

{

    [DataMember]

    public int ICHolesNS

    {

        get

        {

            return GalvEntFunctions.GetIntFromBool(this.ICHoles);

        }

        set

        {

            this.ICHoles = GalvEntFunctions.GetBoolFromInt(value);

        }

    }

 

    [DataMember]

    public int ICDoubleWeldNS

    {

        get

        {

            return GalvEntFunctions.GetIntFromBool(this.ICDoubleWeld);

        }

        set

        {

            this.ICDoubleWeld = GalvEntFunctions.GetBoolFromInt(value);

        }

    }

 

The functions to convert the int to bool and bool to int I used:

public class GalvEntFunctions

{

    public static bool? GetBoolFromInt(int value)

    {

        switch (value)

        {

            case 0: return true;

            case 1: return false;

            default: return null;

        }

    }

 

    public static int GetIntFromBool(bool? value)

    {

        switch (value)

        {

            case true: return 0;

            case false: return 1;

            default: return 2;

        }

    }

}

Now these properties are available on you entities. Mind the name giving. Thats important. Because the serializer will serialize and deserialize in the order of the properties. So in my case cince I'm using it for iOS I added NS at the end to make sure this property is de/serialized after the original property. In my iOS client I only use the NS properties to get what I want.

Now you can use the default Entity generation without messing around in the code afterwards.

On iOS side I can now implement these nullable types very easy with my segmented controls:

    self.selOpeningHolesOK.selectedSegmentIndex = self.currentF06.ICHolesNS;

    self.selLoadingPosibilities.selectedSegmentIndex = self.currentF06.ICLoadingPos;


.Net Self Tracking Entities and JSON

I know this is not the ideal situation, better to use POCO's or simple objects. But when you already have a large structure in place using these self tracking entities, it can be usefull to do this. In my case we had webservices build for .Net client applications. Now we want to reuse this architecture and consume it on IPAD and IPHONE. Can this work? Yes offcourse. First I tried with SOAP, which works well, but there were 3 problems. First I was relying on a object generator tool of SUDZC, second SOAP is taking more data then JSON, third, iOS doesn't support nillable and the generator converted it into simple types. So now I decided to use JSON, use my own objects and reuse the webservices. 

This involved making a new service layer the consumes the same process layer as my .Net WCF services.

But you will notice that when you try to pass a self tracking entity in JSON format, the webservice will crash. 

The solution to this was:

Set the IsReference flag to false, and remove the DataMember attribute from the ChangeTracker.

//[DataMember]

public ObjectChangeTracker ChangeTracker

and

[DataContract(IsReference = false)]

public partial class GP_MES_Clients: IObjectWithChangeTracker, INotifyPropertyChanged

After this all is changed, you can consume this service in your iOS application!


Passing SQL parameters through WCF

 Passing SQL parameters through WCF. (cannot be serialized because serialization of indexed properties is not supported.)

 

I was having a problem with this, because of serialization. I was using a Dictionary base object to transport the parameters and at the server side I connected these parameters to the SQL command.  So after visiting this site: http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/467b92bf-42e4-490d-ae92-1b1bc74d0daa/ I came up with a good solution.

 

The class containing the parameters looks like this:

 

using System;

 

using System.Collections;

 

using System.Runtime.Serialization;

 

using System.Collections.Generic;

 

 

 

 

 

namespace GP_Global

 

{

 

    /// <summary>

 

    /// The Collection of columns and their values

 

    /// </summary>

 

    /// <remarks>A pair value that holds column names and their values</remarks>

 

    [DataContract]

 

    public class ColumnCollection

 

    {

 

 

 

        [DataMember]

 

        private Dictionary<string, object> _dictonary;

 

 

 

 

 

        //private _dictionary

 

        /// <summary>

 

        /// Default constructor of ColumnCollection

 

        /// </summary>

 

        public ColumnCollection()

 

        {

 

            this._dictonary = new Dictionary<string, object>();

 

        }

 

 

 

        /// <summary>

 

        /// Add key = column and value to the Array

 

        /// </summary>

 

        /// <param name="Key"></param>

 

        /// <param name="Value"></param>

 

        public void Add(string Key, object Value)

 

        {

 

            _dictonary.Add(Key, Value);

 

        }

 

 

 

 

 

        /// <summary>

 

        /// Remove Key from collection

 

        /// </summary>

 

        /// <param name="Key"></param>

 

        public void Remove(string Key)

 

        {

 

            _dictonary.Remove(Key);

 

        }

 

 

 

        /// <summary>

 

        /// property that returns the value

 

        /// </summary>

 

        public object this[string Key]

 

        {

 

            get { return _dictonary[Key]; }

 

            set { _dictonary[Key] = value; }

 

        }

 

 

 

        [DataMember]

 

        public Dictionary<string, object> ColumnsEnumerator

 

        {

 

            get

 

            {

 

                return new Dictionary<string, object>(this._dictonary);

 

            }

 

            set

 

            {

 

                //blank on purpose

 

            }

 

        }

 

    }

 

}

 

 

 

On the client side you can use the code something like this:

 

Dim sql As StringBuilder = New StringBuilder

 

        'sql.AppendFormat("Select * from DD_Objects where Object like '{0}' and Server like '{1}' and DB like '{2}'", selectedTable, GetSelectedServer, GetSelectedDatabase)

 

        sql.Append("Select * from DD_Objects where Object like @Object and Server like @Server and DB like @DB")

 

        Dim params As ColumnCollection = New ColumnCollection

 

        params.Add("Object", selectedTable)

 

        params.Add("Server", GetSelectedServer)

 

        params.Add("DB", GetSelectedDatabase)

 

        Try

 

            Return service.GetDataSetWithParams(sql.ToString, "Master", params)

 

            'Return service.GetDataSet(sql.ToString, "Master")

 

        Catch servEx As System.ServiceModel.FaultException

 

            GlobalExceptionHandling.ShowException(servEx)

 

            GenericSingleton(Of ServiceFactory(Of GC_WCF_Library.IGlobalDataAccessService)).GetInstance().ReOpenService()

 

        End Try

 

 

 

On the server side I just add the parameters to the SQL command:

 

 

If Not Params Is Nothing Then

 

            AddParams(cmd, Params)

 

End If

 

           

 

Private Shared Sub AddParams(ByVal cmd As SqlCommand, ByVal Params As ColumnCollection)

 

        Dim Param As SqlParameter

 

        For Each de As System.Collections.Generic.KeyValuePair(Of String, Object) In Params.ColumnsEnumerator

 

            If de.Key.ToString() = "" Then

 

                Throw New ArgumentNullException("Key in parameter collection cannot be empty")

 

            ElseIf de.Value = Nothing Then

 

                Throw New ArgumentNullException("Value in parameter collection cannot be null")

 

            ElseIf de.Value.ToString() = "" Then

 

                Param = New SqlParameter(de.Key.ToString, DBNull.Value)

 

            ElseIf de.Value.ToString() = DateTime.MinValue.ToString() Then

 

                Param = New SqlParameter(de.Key.ToString, DBNull.Value)

 

            Else

 

                Param = New SqlParameter(de.Key.ToString, de.Value)

 

            End If

 

            cmd.Parameters.Add(Param)

 

        Next 

 

    End Sub

 

 Since I'm using the Channel Factory, I do not have to put all members as datamember and still I can use them on client side. So the trick is not to set the indexed property as datamamber!

 


Using a singleton class to cache the WCF services

I’ve written several articles on generic singleton and WCF. I think it’s very powerfull. After some implementations, the code gets better and better. Together with a collague I created a generic factory to instantiate services. Then I add them to a singleton class to cache them. This works great.

First time I got some trouble with it was because the server only allowed 20 instances of a service for a session. To avoid exceptions messing up the connections, I created a new channel for each call. But the channel is not closed in a good way because of the singleton class. Therefore I needed some good exception handling. For certain exceptions I reopen the service letting go of the previous connection and creating a new connection. Here’s the function that is in the generic service factory (Full code below)that does the trick:

public void HandleWCFException(Exception er)

{

if (er.GetType() == typeof(System.TimeoutException) || er.GetType() == typeof(System.ServiceModel.FaultException))

{

             ReOpenService();

}

       else

       {

             throw er;

}

}

 

public void ReOpenService()

{

if (_fact.State == CommunicationState.Opened)

       {

             try

              {

                    _fact.Close();

              }

              catch { }

            }

            _fact = new ChannelFactory<T>(_endPoint);

            _service = _fact.CreateChannel();

       }

}

So the implementation look s like this:

Try 

GenericSingleton(Of ServiceFactory(Of GC_WCF_Library.IGlobalDataAccessService)).GetInstance().Service.SaveDataSet(sql, ds, "OPS")

Catch servEx As System.Exception

If Not GlobalFunctions.HandleWCFErrors(servEx) Then

             Throw servEx

End If

End Try

 

There you can see the Service factory cached in the Generic Singleton class. We call a function called SaveDataSet on the service of type IGlobalDataAccessService. If an exception is caught, we check if it’s an WCF related exception. If so I reopen the service because it could be in the faulted state.

Also make sure you set the close timeout of your service to 2 or 3 seconds, because alse the closing of a service in faulted state could last a long time.

Here the complete code for the generic service factory:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;

 

namespace GP_Global

{

    public class ServiceFactory<T> where T : class

    {

        private T _service;

        private ChannelFactory<T> _fact;

        private string _endPoint;

 

        public ChannelFactory<T> Factory

        {

            get

            {

                if (_fact.State != CommunicationState.Opened)

                {

                    _fact = new ChannelFactory<T>(_endPoint);

                }

                return _fact;

            }

            set { _fact = value; }

        }

 

        public T Service

        {

            get

            {

                if (_fact.State != CommunicationState.Opened)

                {

                    _fact = new ChannelFactory<T>(_endPoint);

                    //fact.Open();

                    _service = _fact.CreateChannel();

                }

                //_service = fact.CreateChannel();

                return _service;

               

            }

            set

            {

                _service = value;

            }

        }

 

        public void HandleWCFException(Exception er)

        {

            if (er.GetType() == typeof(System.TimeoutException) || er.GetType() == typeof(System.ServiceModel.FaultException))

            {

                ReOpenService();

            }

            else

            {

                throw er;

            }

        }

 

        public void ReOpenService()

        {

            if (_fact.State == CommunicationState.Opened)

            {

                try

                {

                    _fact.Close();

                }

                catch { }

            }

            _fact = new ChannelFactory<T>(_endPoint);

            _service = _fact.CreateChannel();

        }

 

        public void InitFactory(string endpoint)

        {

            if (_service == null)

            {

                _fact = new ChannelFactory<T>(endpoint);

                _endPoint = endpoint;

                _service = _fact.CreateChannel();

            }

        }

    }

}


WCF: Conflicts between config file on server and client

A simple trick to fix this problem is to add the service via visual studio's service reference. Then Visual studio will create the configuration settings for you on the client based on the settings of the server and the local machine settings. Sometimes the conflict comes because a setting is not explicitely defined on the server and then it picks up the machine.config settings. So just add the service with visual studio and inspect the settings or just take them over.


Generic WCF service factory. (In combination with generic singleton)

When you are working with WCF. You will find a need to have a factory that will initiate the services for you. To make it generic would be the best option, if your services do not differ to much from configuration implemtation. I will provide you with a class that will initiate any WCF service having its configuration in the config file or simply using the default configuration. It has a init function where you provide the endpoint configuration name.

public class ServiceFactory<T> where T : class

{

    private T _service;

    private ChannelFactory<T> fact;

    public T Service

    {

        get

        {

            if (fact.State != CommunicationState.Opened)

            {

                fact.Open();

            }

            _service = fact.CreateChannel();

            return _service;

        }

     

        set

        {

            _service = value;

        }

    }

    public void InitFactory(string endpoint)

    {

        if (_service == null)

        {

            fact = new ChannelFactory<T>(endpoint);

            _service = fact.CreateChannel();

         }

    }

} 

I used this in combination with a generic singleton, so I only initialize the service once and call the operations from the singleton:

 GenericSingleton<ServiceFactory<INurseryModuleService>>.GetInstance().InitFactory("MyEndPointConfig");

Then I just call the service operations like this:

GenericSingleton<ServiceFactory<INurseryModuleService >>.GetInstance().Service.TestNurseries(new TestNurseriesRequest());