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