I'm changing our webservices to an async model. And for that I have to change over a hundred methods.
Doing it manually is one (unappealing) option. Is there no way to programmatically parse & change multiple functions/code files?
Example:
[Webmethod]
public void MyWebservice (string parameter1, string parameter2, string parameter3)
{
//Logic here
}
And change this to:
public void InternalMyWebservice (string parameter1, string parameter2, string parameter3, AsyncCallback callback)
{
//Logic here
}
[Webmethod]
public void BeginMyWebservice (string parameter1, string parameter2, string parameter3, AsyncCallback callbac开发者_开发知识库k, object asyncState)
{
//Queue InternalMyWebservice in a threadpool
}
public void EndMyWebservice(IAsyncResult asyncResult)
{
//Set return values
}
It's basically the same thing I have to do for each webservice. Change the name to "InternalX", add a parameter and create the begin & end method.
You should be able to use the CSharpCodeProvider.Parse
method to generate a CodeCompileUnit
instance which is a object oriented representation of your code. With that you can drill down to your methods, change the arguments, add new methods and stuff and when you're finished you can save the code back to a textfile. You generate code by calling CodeDomProvider.GenerateCodeFromCompileUnit
passing the modified CodeCompileUnit
Provides access to instances of the C# code generator and code compiler.
Try to find a solution by Resharper and if not do some text replacing by Regex.
You could use a parser generator like ANTLR. Writing an ANTLR grammar for a subset of C# that only parses class and method declarations and simply ignores the method code shouldn't be to hard. Or you could use one of the C# grammars from the ANTLR site.
ANTLR has a feature called a "rewrite grammars" (e.g. look this question) that is very close to what you want to do.
But personally, I wouldn't put the generated methods in one file with the actual methods. If you find a bug in the generated code and want to re-generate them, the parser gets more complex because it has to recognize the methods it generated previously. And the temptation to edit the generated methods is very high. Also, it seems to violate the single-responsibility principle, but that might be a matter of taste.
I would put the generated methods in a separate file (a derived class or a partial class declaration). This has the advantage that you don't need a parser: If the non-generated class file compiles (maybe with abstract or partial method declarations in it), you can compile it and simply use well-known reflection mechanisms to get all the information you want. All you need is a templating framework like StringTemplate or T4 to generate the code.
Why not just write one wrapper class, which will get the objects of existing classes (or delegates) at construction and call the needed methods asynchronously? The methods of the existing classes could still be synchronous.
The below code does the replacement but is heavily dependent on the formatting of the input source file.
Assumptions
- Webmethod begins on a new line with prefix 'public void'
- Parameters are on the same line
- Opening and closing brackets (
{
and}
) are on separate lines.
Code can be optimized and hard coding can be removed.
class CodeChanger
{
private Dictionary webMethodDictionary;
public CodeChanger()
{
webMethodDictionary = new Dictionary();
}
public void ChangeCode(string oldFilePath, string newFilePath)
{
StringBuilder newFileContents = new StringBuilder();
StringBuilder webserviceMethodContents = new StringBuilder();
Encoding iso88591Encoding = Encoding.GetEncoding("ISO-8859-1");
string readLine;
using (StreamReader streamReader = new StreamReader(oldFilePath, iso88591Encoding))
{
while ((readLine = streamReader.ReadLine()) != null)
{
if (!string.IsNullOrEmpty(readLine.Trim()))
{
if (string.Equals(readLine, "[Webmethod]"))
{
// Read the next line - method signature
if ((readLine = streamReader.ReadLine()) != null)
{
readLine = readLine.Trim();
if (readLine.StartsWith("public void"))
{
string methodName = readLine.Split(new char[] { ' ' })[2];
Webmethod webMethod = new Webmethod(methodName);
webMethodDictionary.Add(methodName, webMethod);
// Process parameters
ProcessParameters(readLine, methodName, webMethod);
// Process Body
if ((readLine = streamReader.ReadLine()) != null)
{
StringBuilder methodBody = new StringBuilder();
readLine = readLine.Trim();
if (string.Equals(readLine, "{"))
{
int bracketCounter = 1;
while ((readLine = streamReader.ReadLine()) != null)
{
if (string.Equals(readLine.Trim(), "}"))
{
bracketCounter--;
}
else if (string.Equals(readLine.Trim(), "{"))
{
bracketCounter++;
}
if (bracketCounter != 0)
{
methodBody.AppendLine(readLine);
}
else
{
break;
}
}
webMethod.AddBody(methodBody.ToString());
}
}
newFileContents.AppendLine(GenerateNewWebmethods(webMethod));
}
}
}
else
{
newFileContents.AppendLine(readLine);
}
}
else
{
newFileContents.AppendLine();
}
}
}
using (StreamWriter writer = new StreamWriter(newFilePath, false, iso88591Encoding))
{
writer.Write(newFileContents.ToString());
}
}
private static void ProcessParameters(string readLine, string methodName, Webmethod webMethod)
{
int positionOpenBrackets = string.Concat("public void ", methodName, " ").Length;
string parametersString = readLine.Substring(positionOpenBrackets).Trim();
parametersString = parametersString.TrimStart(new char[] { '(' });
parametersString = parametersString.TrimEnd(new char[] { ')' });
string[] parameters = parametersString.Split(new char[] { ',' });
foreach (string parameter in parameters)
{
string[] splitParameters = parameter.Trim().Split(new char[] { ' ' });
webMethod.AddParameter(splitParameters[0].Trim(), splitParameters[1].Trim());
}
}
private string GenerateNewWebmethods(Webmethod webmethod)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine(GenerateInternal(webmethod));
stringBuilder.AppendLine(GenerateBegin(webmethod));
stringBuilder.Append(GenerateEnd(webmethod));
return stringBuilder.ToString();
}
private string GenerateInternal(Webmethod webmethod)
{
StringBuilder stringBuilder = new StringBuilder();
string parametersString = GenerateParameterString(webmethod);
stringBuilder.AppendLine(string.Format("public void Internal{0} ({1}, AsyncCallback callback)",
webmethod.Name, parametersString.Trim().TrimEnd(',')));
stringBuilder.AppendLine("{");
stringBuilder.Append(webmethod.Body);
stringBuilder.AppendLine("}");
return stringBuilder.ToString();
}
private string GenerateEnd(Webmethod webmethod)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine(string.Format("public void End{0} (IAsyncResult asyncResult)", webmethod.Name));
stringBuilder.AppendLine("{");
stringBuilder.AppendLine("//Set return values");
stringBuilder.Append("}");
return stringBuilder.ToString();
}
private string GenerateBegin(Webmethod webmethod)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("[Webmethod]");
string parametersString = GenerateParameterString(webmethod);
stringBuilder.AppendLine(string.Format("public void Begin{0} ({1}, AsyncCallback callback, object asyncState)",
webmethod.Name, parametersString.Trim().TrimEnd(',')));
stringBuilder.AppendLine("{");
stringBuilder.AppendLine("//Queue InternalMyWebservice in a threadpool");
stringBuilder.AppendLine("}");
return stringBuilder.ToString();
}
private static string GenerateParameterString(Webmethod webmethod)
{
StringBuilder parametersStringBuilder = new StringBuilder();
foreach (MethodParameter parameter in webmethod.Parameters)
{
string parameterString = string.Concat(parameter.Type, " ", parameter.Name, ", ");
parametersStringBuilder.Append(parameterString);
}
return parametersStringBuilder.ToString();
}
}
class Webmethod
{
public IList Parameters { get; private set; }
public string Name { get; private set; }
public string Body { get; private set; }
public Webmethod(string name)
{
Parameters = new List();
Name = name;
}
public void AddParameter(string paramType, string paramName)
{
MethodParameter methodParameter = new MethodParameter
{
Type = paramType,
Name = paramName
};
Parameters.Add(methodParameter);
}
public void AddBody(string body)
{
Body = body;
}
}
class MethodParameter
{
public string Type { get; set; }
public string Name { get; set; }
}
Usage
CodeChanger cc = new CodeChanger();
cc.ChangeCode(@"D:\1.cs", @"D:\3.cs");
This can also be modified to suit the System.CodeDom approach.
I am actually not too familiar with the .NET framework, but this could definitely be accomplished with Regular Expressions.
精彩评论