THRIFT-1489 Add support for WCF bindings (optionally) to C# compiler, allowing web service usage of Thrift generated code
Patch: Kieran Benton

changes by roger:
- use ServiceModel, DataContract only when wcf is enabled
- indent space vs tab
- remove issue on lib/cpp/README_WINDOWS
- add testStringMap on test/csharp/ThriftTest/TestServer.cs
- add build to test/csharp/ThriftTest/maketest.sh

git-svn-id: https://svn.apache.org/repos/asf/thrift/trunk@1232578 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/.gitignore b/.gitignore
index 24529c9..9445209 100644
--- a/.gitignore
+++ b/.gitignore
@@ -231,3 +231,9 @@
 /test/rb/Makefile.in
 /thrift.exe
 /ylwrap
+/compiler/cpp/Debug
+*.suo
+*.cache
+*.user
+*.ipch
+*.sdf
\ No newline at end of file
diff --git a/compiler/cpp/src/generate/t_csharp_generator.cc b/compiler/cpp/src/generate/t_csharp_generator.cc
index 9f882e6..ca266e7 100644
--- a/compiler/cpp/src/generate/t_csharp_generator.cc
+++ b/compiler/cpp/src/generate/t_csharp_generator.cc
@@ -50,6 +50,12 @@
       iter = parsed_options.find("async");
       async_ctp_ = (iter != parsed_options.end());
 
+	  iter = parsed_options.find("wcf");
+	  wcf_ = (iter != parsed_options.end());
+	  if (wcf_) {
+		  wcf_namespace_ = iter->second;
+	  }
+
       out_dir_base_ = "gen-csharp";
     }
     void init_generator();
@@ -62,8 +68,8 @@
     void generate_struct (t_struct* tstruct);
     void generate_xception (t_struct* txception);
     void generate_service (t_service* tservice);
-    void generate_property(ofstream& out, t_field* tfield, bool isPublic);
-    void generate_csharp_property(ofstream& out, t_field* tfield, bool isPublic, std::string fieldPrefix = "");
+    void generate_property(ofstream& out, t_field* tfield, bool isPublic, bool generateIsset);
+    void generate_csharp_property(ofstream& out, t_field* tfield, bool isPublic, bool includeIsset=true, std::string fieldPrefix = "");
     bool print_const_value (std::ofstream& out, std::string name, t_type* type, t_const_value* value, bool in_static, bool defval=false, bool needtype=false);
     std::string render_const_value(std::ofstream& out, std::string name, t_type* type, t_const_value* value);
     void print_const_constructor(std::ofstream& out, std::vector<t_const*> consts);
@@ -71,6 +77,7 @@
 
     void generate_csharp_struct(t_struct* tstruct, bool is_exception);
     void generate_csharp_struct_definition(std::ofstream& out, t_struct* tstruct, bool is_xception=false, bool in_class=false, bool is_result=false);
+    void generate_csharp_wcffault(std::ofstream& out, t_struct* tstruct);
     void generate_csharp_struct_reader(std::ofstream& out, t_struct* tstruct);
     void generate_csharp_struct_result_writer(std::ofstream& out, t_struct* tstruct);
     void generate_csharp_struct_writer(std::ofstream& out, t_struct* tstruct);
@@ -96,6 +103,11 @@
     void generate_serialize_set_element (std::ofstream& out, t_set* tmap, std::string iter);
     void generate_serialize_list_element (std::ofstream& out, t_list* tlist, std::string iter);
 
+    void generate_csharp_doc (std::ofstream& out, t_field*    field);
+    void generate_csharp_doc (std::ofstream& out, t_doc*      tdoc);
+    void generate_csharp_doc (std::ofstream& out, t_function* tdoc);
+    void generate_csharp_docstring_comment (std::ofstream &out, string contents);
+
     void start_csharp_namespace (std::ofstream& out);
     void end_csharp_namespace (std::ofstream& out);
 
@@ -112,6 +124,7 @@
     std::string argument_list(t_struct* tstruct);
     std::string type_to_enum(t_type* ttype);
     std::string prop_name(t_field* tfield);
+    std::string get_enum_class_name(t_type* type);
 
     bool type_can_be_null(t_type* ttype) {
       while (ttype->is_typedef()) {
@@ -128,7 +141,9 @@
     std::string namespace_name_;
     std::ofstream f_service_;
     std::string namespace_dir_;
-	bool async_ctp_;
+    bool async_ctp_;
+    bool wcf_;
+    std::string wcf_namespace_;
 };
 
 
@@ -176,7 +191,9 @@
     "using System.IO;\n" +
 	(async_ctp_ ? "using System.Threading.Tasks;\n" : "") +
     "using Thrift;\n" +
-    "using Thrift.Collections;\n";
+    "using Thrift.Collections;\n" +
+        (wcf_ ? "using System.ServiceModel;\n" : "") +
+    "using System.Runtime.Serialization;\n";
 }
 
 string t_csharp_generator::csharp_thrift_usings() {
@@ -200,6 +217,8 @@
 
   start_csharp_namespace(f_enum);
 
+  generate_csharp_doc(f_enum, tenum);
+
   indent(f_enum) <<
     "public enum " << tenum->get_name() << "\n";
   scope_up(f_enum);
@@ -207,6 +226,8 @@
   vector<t_enum_value*> constants = tenum->get_constants();
   vector<t_enum_value*>::iterator c_iter;
   for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {
+	generate_csharp_doc(f_enum, *c_iter);
+
     int value = (*c_iter)->get_value();
     indent(f_enum) << (*c_iter)->get_name() << " = " << value << "," << endl;
   }
@@ -239,6 +260,7 @@
   vector<t_const*>::iterator c_iter;
   bool need_static_constructor = false;
   for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) {
+	generate_csharp_doc(f_consts, (*c_iter));
     if (print_const_value(f_consts, (*c_iter)->get_name(), (*c_iter)->get_type(), (*c_iter)->get_value(), false)) {
       need_static_constructor = true;
     }
@@ -403,7 +425,7 @@
   f_struct <<
     autogen_comment() <<
     csharp_type_usings() <<
-    csharp_thrift_usings();
+    csharp_thrift_usings() << endl;
 
   generate_csharp_struct_definition(f_struct, tstruct, is_exception);
 
@@ -417,8 +439,14 @@
   }
 
   out << endl;
+
+  generate_csharp_doc(out, tstruct);
+
   indent(out) << "#if !SILVERLIGHT" << endl;
   indent(out) << "[Serializable]" << endl; 
+  if (wcf_ &&!is_exception) {
+	  indent(out) << "[DataContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl; // do not make exception classes directly WCF serializable, we provide a seperate "fault" for that
+  }
   indent(out) << "#endif" << endl;
   bool is_final = (tstruct->annotations_.find("final") != tstruct->annotations_.end());
  
@@ -444,7 +472,8 @@
   out << endl;
 
   for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
-    generate_property(out, *m_iter, true);
+	generate_csharp_doc(out, *m_iter);
+    generate_property(out, *m_iter, true, true);
   }
 
   if (members.size() > 0) {
@@ -452,7 +481,11 @@
       endl <<
       indent() << "public Isset __isset;" << endl <<
       indent() << "#if !SILVERLIGHT" << endl <<
-      indent() << "[Serializable]" << endl <<
+      indent() << "[Serializable]" << endl;
+      if (wcf_) {
+	  indent(out) << "[DataContract]" << endl;
+      }
+    out <<
       indent() << "#endif" << endl <<
       indent() << "public struct Isset {" << endl;
     indent_up();
@@ -491,12 +524,47 @@
   scope_down(out);
   out << endl;
 
+  // generate a corresponding WCF fault to wrap the exception
+  if(wcf_ && is_exception) {
+	  generate_csharp_wcffault(out, tstruct);
+  }
+
   if (!in_class)
   {
     end_csharp_namespace(out);
   }
 }
 
+void t_csharp_generator::generate_csharp_wcffault(ofstream& out, t_struct* tstruct) {
+  out << endl;
+  indent(out) << "#if !SILVERLIGHT" << endl;
+  indent(out) << "[Serializable]" << endl;
+  indent(out) << "[DataContract]" << endl;
+  indent(out) << "#endif" << endl;
+  bool is_final = (tstruct->annotations_.find("final") != tstruct->annotations_.end());
+
+  indent(out) << "public " << (is_final ? "sealed " : "") << "partial class " << tstruct->get_name() << "Fault" << endl;
+
+  scope_up(out);
+
+  const vector<t_field*>& members = tstruct->get_members();
+  vector<t_field*>::const_iterator m_iter;
+
+  // make private members with public Properties
+  for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+    indent(out) <<
+    "private " << declare_field(*m_iter, false, "_") << endl;
+  }
+  out << endl;
+
+  for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+    generate_property(out, *m_iter, true, false);
+  }
+
+  scope_down(out);
+  out << endl;
+}
+
 void t_csharp_generator::generate_csharp_struct_reader(ofstream& out, t_struct* tstruct) {
   indent(out) <<
     "public void Read (TProtocol iprot)" << endl;
@@ -753,7 +821,7 @@
   f_service_ <<
     autogen_comment() <<
     csharp_type_usings() <<
-    csharp_thrift_usings();
+    csharp_thrift_usings() << endl;
 
   start_csharp_namespace(f_service_);
 
@@ -782,13 +850,34 @@
     extends_iface = " : " + extends + ".Iface";
   }
 
+  generate_csharp_doc(f_service_, tservice);
+
+  if (wcf_) {
+	  indent(f_service_) <<
+		"[ServiceContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl;
+  }
   indent(f_service_) <<
     "public interface Iface" << extends_iface << " {" << endl;
+
   indent_up();
   vector<t_function*> functions = tservice->get_functions();
   vector<t_function*>::iterator f_iter;
   for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter)
   {
+	generate_csharp_doc(f_service_, *f_iter);
+
+	// if we're using WCF, add the corresponding attributes
+	if (wcf_) {
+		indent(f_service_) <<
+			"[OperationContract]" << endl;
+
+		const std::vector<t_field*>& xceptions = (*f_iter)->get_xceptions()->get_members();
+		vector<t_field*>::const_iterator x_iter;
+		for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) {
+		  indent(f_service_) << "[FaultContract(typeof(" + type_name((*x_iter)->get_type(), false, false) + "Fault))]" << endl;
+		}
+	}
+
     indent(f_service_) <<
       function_signature(*f_iter) << ";" << endl;
     indent(f_service_) << "#if SILVERLIGHT" << endl;
@@ -1664,10 +1753,13 @@
   generate_serialize_field(out, &efield, "");
 }
 
-void t_csharp_generator::generate_property(ofstream& out, t_field* tfield, bool isPublic) {
-    generate_csharp_property(out, tfield, isPublic, "_");
+void t_csharp_generator::generate_property(ofstream& out, t_field* tfield, bool isPublic, bool generateIsset) {
+    generate_csharp_property(out, tfield, isPublic, generateIsset, "_");
 }
-void t_csharp_generator::generate_csharp_property(ofstream& out, t_field* tfield, bool isPublic, std::string fieldPrefix) {
+void t_csharp_generator::generate_csharp_property(ofstream& out, t_field* tfield, bool isPublic, bool generateIsset, std::string fieldPrefix) {
+	if(wcf_ && isPublic) {
+		indent(out) << "[DataMember]" << endl;
+	}
     indent(out) << (isPublic ? "public " : "private ") << type_name(tfield->get_type())
                 << " " << prop_name(tfield) << endl;
     scope_up(out);
@@ -1677,7 +1769,9 @@
     scope_down(out);
     indent(out) << "set" << endl;
     scope_up(out);
-    indent(out) << "__isset." << tfield->get_name() << " = true;" << endl;
+    if (generateIsset) {
+		indent(out) << "__isset." << tfield->get_name() << " = true;" << endl;
+	}
     indent(out) << "this." << fieldPrefix + tfield->get_name() << " = value;" << endl;
     scope_down(out);
     scope_down(out);
@@ -1870,7 +1964,64 @@
   throw "INVALID TYPE IN type_to_enum: " + type->get_name();
 }
 
+void t_csharp_generator::generate_csharp_docstring_comment(ofstream &out, string contents) {
+  generate_docstring_comment(out,
+                             "/// <summary>\n",
+                             "/// ", contents,
+                             "/// </summary>\n");
+
+
+}
+
+void t_csharp_generator::generate_csharp_doc(ofstream &out, t_field* field) {
+  if (field->get_type()->is_enum()) {
+    string combined_message = field->get_doc() + "\n<seealso cref=\"" + get_enum_class_name(field->get_type()) + "\"/>";
+    generate_csharp_docstring_comment(out, combined_message);
+  } else {
+    generate_csharp_doc(out, (t_doc*)field);
+  }
+}
+
+void t_csharp_generator::generate_csharp_doc(ofstream &out, t_doc* tdoc) {
+  if (tdoc->has_doc()) {
+    generate_csharp_docstring_comment(out, tdoc->get_doc());
+  }
+}
+
+void t_csharp_generator::generate_csharp_doc(ofstream &out, t_function* tfunction) {
+  if (tfunction->has_doc()) {
+	stringstream ps;
+    const vector<t_field*>& fields = tfunction->get_arglist()->get_members();
+    vector<t_field*>::const_iterator p_iter;
+    for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) {
+      t_field* p = *p_iter;
+      ps << "\n<param name=\"" << p->get_name() << "\">";
+      if (p->has_doc()) {
+		std::string str = p->get_doc();
+		str.erase(std::remove(str.begin(), str.end(), '\n'), str.end()); // remove the newlines that appear from the parser
+		ps << str;
+      }
+	  ps << "</param>";
+    }
+    generate_docstring_comment(out,
+                               "",
+                               "/// ",
+							   "<summary>\n" + tfunction->get_doc() + "</summary>" + ps.str(),
+                               "");
+  }
+}
+
+std::string t_csharp_generator::get_enum_class_name(t_type* type) {
+  string package = "";
+  t_program* program = type->get_program();
+  if (program != NULL && program != program_) {
+    package = program->get_namespace("csharp") + ".";
+  }
+  return package + type->get_name();
+}
 
 THRIFT_REGISTER_GENERATOR(csharp, "C#",
-"    async:  add AsyncCTP support.\n")
+"    async:           Adds Async CTP support.\n"
+"    wcf:             Adds bindings for WCF to generated classes.\n"
+)
 
diff --git a/lib/cpp/README_WINDOWS b/lib/cpp/README_WINDOWS
index ad4cac4..3a00b3a 100644
--- a/lib/cpp/README_WINDOWS
+++ b/lib/cpp/README_WINDOWS
@@ -39,9 +39,6 @@
   This library contains the Thrift nonblocking server, which uses libevent.
   To link this library you will also need to link libevent.
 
-You MUST apply this patch to make generated code compile.
-https://issues.apache.org/jira/browse/THRIFT-1139
-
 Linking Against Thrift
 ======================
 
diff --git a/test/csharp/ThriftTest/TestServer.cs b/test/csharp/ThriftTest/TestServer.cs
old mode 100644
new mode 100755
index 894ec9c..6fb75f4
--- a/test/csharp/ThriftTest/TestServer.cs
+++ b/test/csharp/ThriftTest/TestServer.cs
@@ -115,7 +115,27 @@
 				}
 				Console.WriteLine("})");
 				return thing;
-			}
+			}

+

+            public Dictionary<string, string> testStringMap(Dictionary<string, string> thing)

+            {

+                Console.WriteLine("testStringMap({");

+                bool first = true;

+                foreach (string key in thing.Keys)

+                {

+                    if (first)

+                    {

+                        first = false;

+                    }

+                    else

+                    {

+                        Console.WriteLine(", ");

+                    }

+                    Console.WriteLine(key + " => " + thing[key]);

+                }

+                Console.WriteLine("})");

+                return thing;

+            }
 
 			public THashSet<int> testSet(THashSet<int> thing)
 			{
diff --git a/test/csharp/ThriftTest/maketest.sh b/test/csharp/ThriftTest/maketest.sh
index 5580de8..e11b5b2 100755
--- a/test/csharp/ThriftTest/maketest.sh
+++ b/test/csharp/ThriftTest/maketest.sh
@@ -21,3 +21,9 @@
 
 ../../../compiler/cpp/thrift --gen csharp -o . ../../ThriftTest.thrift
 gmcs /t:library /out:./ThriftImpl.dll /recurse:./gen-csharp/* /reference:../../../lib/csharp/Thrift.dll
+gmcs  /out:TestClientServer.exe /reference:../../../lib/csharp/Thrift.dll /reference:ThriftImpl.dll TestClient.cs TestServer.cs Program.cs
+
+export MONO_PATH=../../../lib/csharp/
+
+timeout 120 ./TestClientServer.exe server &
+./TestClientServer.exe client