none
T4 template for Service Operations

    Question

  • We had a need for a t4 template for WCF Data Services 5.0.1 Server Operation support.  The OData T4 on nuget was out of date and as I looked at it, I realized it created all of the client operations necessary for the entire metadata, not just Service Operations.  Now that DataSvcUtil creates a good client class for the metadata, I decided to modify the existing T4 so that it will generate CreateQuery<> and Execute<> for the missing ServiceOperations.  It's meant to be used with the generated client class. A user might have to change namespaces and the metadata location so things work.   

    The class can be used by instantiating the generated class with a System.Data.Services.Client.DataServiceContext. I'm copying the template inline just in case it will help somebody else.  I've just started using it, so it might have some issues, any feedback is welcome.

    -Dana

    <#@ template debug="true" hostSpecific="true" #>
    <#@ output extension=".cs" #>
    <#@ Assembly Name="System.Core.dll" #>
    <#@ Assembly Name="System.Xml.dll" #>
    <#@ Assembly Name="System.Xml.Linq.dll" #>
    <#@ Assembly Name="System.Windows.Forms.dll" #>
    <#@ Assembly name="Microsoft.Data.Edm" #>
    <#@ Assembly name="Microsoft.Data.OData" #>
    <#@ import namespace="System" #>
    <#@ import namespace="System.IO" #>
    <#@ import namespace="System.Diagnostics" #>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Xml"#>
    <#@ import namespace="System.Xml.Linq" #>
    <#@ import namespace="System.Collections" #>
    <#@ import namespace="System.Collections.Generic" #>
    <#@ import namespace="Microsoft.Data.Edm.Csdl" #>
    <#@ import namespace="Microsoft.Data.Edm" #>
    <#@ import namespace="Microsoft.Data.Edm.Annotations" #>
    <#@ import namespace="System.Data.Services.Common"#>
    <#@ import namespace="System.Text"#>
    <#
    
    if (TransformContext == null)
    {
    	TransformContext= new CodeGenerationContext
    	{
    		Namespace = "ServiceReference",
    		MetadataFilepath = System.IO.Path.GetDirectoryName(this.Host.TemplateFile) + @"\Metadata.edmx"
    	};
    	TransformContext.LoadEdmModelFromFile();
    }
    
    BeginWriteNamespace();
    
    IEdmEntityContainer container = TransformContext.EdmModel.EntityContainers().FirstOrDefault<IEdmEntityContainer>();
    
    var entitySetQuery = from item in container.Elements
    where item.ContainerElementKind == EdmContainerElementKind.EntitySet
    select item as IEdmEntitySet;
    
    // Write DataServiceContext 
    WriteTypeDeclaration(container);
    WriteContextConstructor(container);
    WriteServiceOperations(container);
    EndWriteType();
    EndWriteNamespace();
    #>
    <#+
    	void BeginWriteNamespace()
    	{
    #>
    //------------------------------------------------------------------------------
    // <auto-generated>
    //    This code was generated from a template.
    //
    //    Manual changes to this file may cause unexpected behavior in your application.
    //    Manual changes to this file will be overwritten if the code is regenerated.
    // </auto-generated>
    //------------------------------------------------------------------------------
    
    namespace <#=TransformContext.Namespace #>	
    {
    	<#+
    	}
    	void EndWriteNamespace()
    	{
    	#>
    }
    	<#+
    	}
    void WriteTypeDeclaration(IEdmEntityContainer container )
    	{
    	#>
    public class ServiceOperationClient
        {
    	<#+
    	}
    
    	void EndWriteType()
    	{
    	#>
        }
    <#+
    	}
    
    	void WriteContextConstructor(IEdmEntityContainer container)
    	{
    #>
    	public ServiceOperationClient(System.Data.Services.Client.DataServiceContext dataServiceContext)
            {
                this.DataServiceContext = dataServiceContext;
            }
    
    	    public System.Data.Services.Client.DataServiceContext DataServiceContext {get; set;}
    
    <#+
    	}
    	
    
    void WriteServiceOperations(IEdmEntityContainer container)
    {
        var svcOps = from item in container.Elements
                                         where item.ContainerElementKind == EdmContainerElementKind.FunctionImport
                                         select item as IEdmFunctionImport;	
    	foreach (IEdmFunctionImport svcOp in svcOps)
        {
    					
    		string parameters=String.Empty;
    		string queryOptions=String.Empty;
    		string returnType="System.Data.Services.Client.OperationResponse";
    				
    		if (svcOp.ReturnType==null)
    		{
    		    GetServiceOperationParameters(svcOp,true,out parameters,out queryOptions);
    			string svcOpUri=string.Format("{0}?{1}",svcOp.Name,queryOptions);
    			if (!string.IsNullOrEmpty(queryOptions))
    			{
    				svcOpUri=string.Format("{0}?{1}",svcOp.Name,queryOptions);	
    			}
    			else
    			{
    				svcOpUri=svcOp.Name;
    			}	
    #>		public <#=returnType#> <#=svcOp.Name#>(<#=parameters#>)
    		{ 			
    	 		return DataServiceContext.Execute(new global::System.Uri("<#=svcOpUri#>",global::System.UriKind.Relative),"Get");
    		}	
    <#+     
    		}
    		else
    		{
    			if (svcOp.ReturnType.Definition.TypeKind == EdmTypeKind.Collection)
    	        {
    				
    				GetServiceOperationParameters(svcOp,true,out parameters,out queryOptions);
    	            IEdmCollectionType collectionType = svcOp.ReturnType.Definition as IEdmCollectionType;
    				if (collectionType.ElementType.Definition.TypeKind == EdmTypeKind.Primitive)
    				{
    					IEdmPrimitiveType primitiveType=collectionType.ElementType.Definition as IEdmPrimitiveType;
    					returnType= TranslateEdmType(primitiveType.PrimitiveKind);
    					string svcOpUri=string.Format("{0}?{1}",svcOp.Name,queryOptions);
    					if (!string.IsNullOrEmpty(queryOptions))
    					{
    						svcOpUri=string.Format("{0}?{1}",svcOp.Name,queryOptions);	
    					}
    					else
    					{
    						svcOpUri=svcOp.Name;
    					}
    
    #>		public System.Collections.Generic.IEnumerable<<#=returnType#>> <#=svcOp.Name#>(<#=parameters#>)
    		{ 
    			return (System.Collections.Generic.IEnumerable<<#=returnType#>>)this.Execute< <#=returnType#>> (new global::System.Uri("<#= svcOpUri#>",global::System.UriKind.Relative),"Get");
    		}
    <#+      
    				}
    				else if (collectionType.ElementType.Definition.TypeKind == EdmTypeKind.Complex)
    				{
    					GetServiceOperationParameters(svcOp,true,out parameters,out queryOptions);
    	            	returnType=GetNameFromFullName(collectionType.ElementType.FullName());
    					string svcOpUri=string.Format("{0}?{1}",svcOp.Name,queryOptions);
    					if (!string.IsNullOrEmpty(queryOptions))
    					{
    						svcOpUri=string.Format("{0}?{1}",svcOp.Name,queryOptions);	
    					}
    					else
    					{
    						svcOpUri=svcOp.Name;
    					}
    #>		public  System.Collections.Generic.IEnumerable<<#=returnType#>> <#=svcOp.Name#>(<#=parameters#>)
    		{ 
    			return (System.Collections.Generic.IEnumerable<<#=returnType#>>)DataServiceContext.Execute< <#=returnType#>> (new global::System.Uri("<#= svcOpUri#>",global::System.UriKind.Relative),"Get");
    		}
    <#+      
    				}
    				else
    				{
    					GetServiceOperationParameters(svcOp,false,out parameters,out queryOptions);
    	            	returnType=GetNameFromFullName(collectionType.ElementType.FullName());
    #>		public global::System.Data.Services.Client.DataServiceQuery<<#=returnType#>> <#=svcOp.Name#>(<#=parameters#>)
    		{ 
    	 		return DataServiceContext.CreateQuery<<#=returnType#>>("<#=svcOp.Name#>")<#=queryOptions#>;
    		}
    
    <#+     
    					
    				}
    				continue;
    			}
    			else if (svcOp.ReturnType.Definition.TypeKind == EdmTypeKind.Primitive)
    			{
    				GetServiceOperationParameters(svcOp,true,out parameters,out queryOptions);
    				IEdmPrimitiveType primitiveType=svcOp.ReturnType.Definition as IEdmPrimitiveType;
    				returnType= TranslateEdmType(primitiveType.PrimitiveKind);
    			}
    			else if (svcOp.ReturnType.Definition.TypeKind == EdmTypeKind.Complex)
    			{
    				GetServiceOperationParameters(svcOp,true,out parameters,out queryOptions);
    				IEdmComplexType complexType=svcOp.ReturnType.Definition as IEdmComplexType;
    				returnType=GetNameFromFullName(complexType.FullName());				
    			}
    			else if (svcOp.ReturnType.Definition.TypeKind == EdmTypeKind.Entity)
    			{
    				GetServiceOperationParameters(svcOp,true,out parameters,out queryOptions);
    				IEdmEntityType entityType=svcOp.ReturnType.Definition as IEdmEntityType;
    				returnType=GetNameFromFullName(entityType.FullName());				
    				
    			}
    			string uriParameter;
    			if (!string.IsNullOrEmpty(queryOptions))
    			{
    				uriParameter=string.Format("{0}?{1}",svcOp.Name,queryOptions);	
    			}
    			else
    			{
    				uriParameter=svcOp.Name;
    			}
    #>		public <#=returnType#> <#=svcOp.Name#>(<#=parameters#>)
    		{
    			System.Data.Services.Client.QueryOperationResponse< <#=returnType#>> response =DataServiceContext.Execute< <#=returnType#> >(new global::System.Uri("<#= uriParameter#>",global::System.UriKind.Relative),"Get");
    			System.Collections.IEnumerator enumerator= response.GetEnumerator();
    			enumerator.MoveNext();
    			return (<#=returnType#>)enumerator.Current;
    		}
    <#+      
    	}
    }
    }
    
    void GetServiceOperationParameters(IEdmFunctionImport svcOp, bool useExecute, out string svcOpParameters,out string svcOpQueryOptions)
    {
    	int paramCount = svcOp.Parameters.Count();
    	string parameters=String.Empty;
    	string queryOptions=String.Empty;	
        foreach (IEdmFunctionParameter p in svcOp.Parameters)
        {
    		parameters=string.Concat(parameters,"global::System." + GetNameFromFullName(p.Type.FullName()));
    		parameters=string.Concat(parameters," ");
    		parameters=string.Concat(parameters,p.Name);
    		if (useExecute)
    		{
    			queryOptions=string.Concat(queryOptions,string.Format("{0} ='\" + {0} + \"'", p.Name));
    		}
    		else
    		{
    		
    			if (GetNameFromFullName(p.Type.FullName()) == "DateTime")
    			{
    				queryOptions=string.Concat(queryOptions,string.Format(".AddQueryOption(\"{0}\", \"datetime'\" + {0}.ToString(\"s\") + \"'\")", p.Name));
    			}
    			else if (GetNameFromFullName(p.Type.FullName()) == "Int32")
    			{
    				queryOptions=string.Concat(queryOptions,string.Format(".AddQueryOption(\"{0}\", \"\" + {0} + \"\")", p.Name));
    			}
    			else if (GetNameFromFullName(p.Type.FullName()) == "Guid")
    			{
    				queryOptions=string.Concat(queryOptions,string.Format(".AddQueryOption(\"{0}\", \"guid'\" + {0}.ToString() + \"'\")", p.Name));
    			}
    			else
    			{
    				queryOptions=string.Concat(queryOptions,string.Format(".AddQueryOption(\"{0}\", \"'\" + {0} + \"'\")", p.Name));
    			}
    
    		}
    					
        	--paramCount;
    		if (paramCount!=0)
    		{
    			parameters= string.Concat(parameters,", ");
    			if (useExecute)
    			{
    				queryOptions=string.Concat(queryOptions,"&");
    			}
    		}				                       
    
        }
    	svcOpParameters=parameters;
    	svcOpQueryOptions=queryOptions;
    }
    		
        string LowerCaseFirstCharecter(string text)
    	{
    		char[] tmpBuffer = text.ToCharArray();	
    		tmpBuffer[0]=text[0].ToString().ToLowerInvariant().ToCharArray()[0];
    		return new String(tmpBuffer);
    	}
    	string TranslateEdmType(EdmPrimitiveTypeKind kind)
    	{
    		string type="UNKNOWN";
    		if (kind==EdmPrimitiveTypeKind.Int32)
    		{
    			type= "int";
    		}
    		else if (kind== EdmPrimitiveTypeKind.String)
    		{
    			type= "string";	
    		}
    		else if (kind==EdmPrimitiveTypeKind.Binary)
    		{
    			type= "byte[]";
    		}
    		else if (kind==EdmPrimitiveTypeKind.Decimal)
    		{
    			type= "decimal";
    		}
    		else if (kind==EdmPrimitiveTypeKind.Int16)
    		{
    			type= "short";	
    		}
    		else if(kind==EdmPrimitiveTypeKind.Single)
    		{	
    			type= "float";
    		}
    		else if (kind==EdmPrimitiveTypeKind.Boolean)
    		{
    			type= "bool";	
    		}
    		else if (kind== EdmPrimitiveTypeKind.DateTime)
    		{
    			type= "global::System.DateTime";
    		}
    		else if (kind== EdmPrimitiveTypeKind.Double)
    		{
    			type= "double";
    		}
    		else if (kind== EdmPrimitiveTypeKind.Guid)
    		{
    			type= "global::System.Guid";
    		}
    		else if (kind== EdmPrimitiveTypeKind.Byte)
    		{
    			type="byte";
    		}
    		else if (kind== EdmPrimitiveTypeKind.Int64)
    		{
    			type="long";
    		}
    		else if (kind== EdmPrimitiveTypeKind.SByte)
    		{
    			type="sbyte";
    		}
    		else if (kind == EdmPrimitiveTypeKind.Stream)
    		{
    			type="global::System.Data.Services.Client.DataServiceStreamLink";
    		}
    		
    		
    	return type;
    }
    	
    string GetNameFromFullName(string fullname)
    {			
    	string[] fullNameParts=fullname.Split('.');
    		 
    	return fullNameParts[fullNameParts.Length-1];
    }
    	
    public CodeGenerationContext TransformContext{get;set;}	
    	
    public class CodeGenerationContext
    {
    	public string Namespace{get;set;}
    	public string MetadataFilepath{get;set;}
    	public string MaxProtocolVersion{get;set;}
    	public IEdmModel EdmModel {get;set;}
    	public CodeGenerationContext()
    	{
    	}
    	public void LoadEdmModelFromString(string edmxContent)
    	{
    		IEdmModel model;
    		IEnumerable<Microsoft.Data.Edm.Validation.EdmError> errors;
    			using (StringReader stringReader = new StringReader(edmxContent))
    		{
    			using (XmlReader xmlReader = XmlReader.Create(stringReader))
    			{
    				bool parsed = EdmxReader.TryParse(xmlReader, out model, out errors);
    				if (!parsed)
    				{
    					throw new Exception(errors.FirstOrDefault().ErrorMessage);	
    				}
    
    				EdmModel=model;
    			}
    		}
            // EDM lib has a bug in October CTP for reading the DataServiceVersion
    		XmlDocument doc = new XmlDocument();
    		doc.LoadXml(edmxContent);                        
    	    foreach (XmlAttribute attribute in doc.DocumentElement.ChildNodes[0].Attributes)
    	    {
    	        if (attribute.LocalName=="MaxDataServiceVersion")
    	        {
    				this.MaxProtocolVersion=string.Format("global::System.Data.Services.Common.DataServiceProtocolVersion.V",attribute.Value[0]);                    
    	        }
    	    }
    	}
    	public void LoadEdmModelFromFile()
    	{
    		IEdmModel model;
    		IEnumerable<Microsoft.Data.Edm.Validation.EdmError> errors;
    			
            FileStream fs = new FileStream(MetadataFilepath, FileMode.Open, FileAccess.Read);
            XmlReader reader = XmlReader.Create(fs, new XmlReaderSettings() { ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, ValidationType = System.Xml.ValidationType.None });
                
            bool parsed = EdmxReader.TryParse(reader, out model, out errors);
    		if (!parsed)
    		{
    			throw new Exception(errors.FirstOrDefault().ErrorMessage);	
    		}
    		EdmModel=model;
    		// EDM lib has a bug in October CTP for reading the DataServiceVersion
    		XmlDocument doc = new XmlDocument();
    		doc.Load(MetadataFilepath);                 
            foreach (XmlAttribute attribute in doc.DocumentElement.ChildNodes[0].Attributes)
            {
               	if (attribute.LocalName=="MaxDataServiceVersion")
                {
    				this.MaxProtocolVersion=string.Format("global::System.Data.Services.Common.DataServiceProtocolVersion.V{0}",attribute.Value[0]);                    
                }
            }
    
    	}
    }
    	
    #>


    Friday, June 01, 2012 4:06 PM

All replies

  • This is great!, i will try this and let you know :)
    Wednesday, July 18, 2012 2:19 PM
  • Hi Dana

    I am looking for feasibility of creating WCF generator using T4 template covering following:

    • Visual Studio Add-in Template – Titled “WCF Generator”
    • Dynamic generation of class file from Linq to SQL based on Database connection information and database name.
    • Dynamic Generation of WCF Service by including above bulleted class file’s methods and properties.

    Could you please provide few inputs on this.

    Thanks,
    Ravi Ayitha

    Tuesday, August 07, 2012 11:44 AM