using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Xml.Linq; using Codelyzer.Analysis; using Codelyzer.Analysis.Model; using CTA.FeatureDetection.Common.Extensions; using CTA.FeatureDetection.Common.Models.WCF; using CTA.FeatureDetection.Common.WCFConfigUtils; using CTA.Rules.Common.WebConfigManagement; using CTA.Rules.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Constants = CTA.Rules.PortCore.WCF.Constants; namespace CTA.Rules.PortCore { public class WCFServicePort { private string _projectPath; private ProjectType _projectType; private AnalyzerResult _analyzerResult; public WCFServicePort(string projectPath, ProjectType projectType, AnalyzerResult analyzerResult) { _projectPath = projectPath; _projectType = projectType; _analyzerResult = analyzerResult; } /// /// Generate New Config File for CoreWCF. /// /// New Config File for CoreWCF public string GetNewConfigFile() { var webConfig = WebConfigManager.LoadWebConfigAsXDocument(_projectPath); var appConfig = WebConfigManager.LoadAppConfigAsXDocument(_projectPath); WebConfigXDocument config; if (webConfig.ContainsElement(Constants.SystemServiceModelElementPath)) { config = webConfig; } else if (appConfig.ContainsElement(Constants.SystemServiceModelElementPath)) { config = appConfig; } else { return null; } var wcfConfig = config.GetDescendantsAndSelf(Constants.SystemServiceModelElement); if (!wcfConfig.IsNullOrEmpty()) { var newXDoc = new XDocument(new XDeclaration(Constants.ConfigXMLVersion, Constants.ConfigXMLEncoding, Constants.ConfigXMLStandalone), wcfConfig.First()); newXDoc.Descendants().Where(d => d.Name == Constants.HostElement).Remove(); newXDoc.Descendants().Where(d => d.Name == Constants.EndpointElement && d.Attribute(Constants.BindingAttribute)?.Value == Constants.MexBinding).Remove(); var serviceModelElement = newXDoc.Element(Constants.SystemServiceModelElement); serviceModelElement.ReplaceWith(new XElement(Constants.ConfigurationElement, serviceModelElement)); var newConfigFile = new StringWriter(); newXDoc.Save(newConfigFile); return newConfigFile.ToString(); } return config.GetDocAsString(); } /// /// Get Config File Path for the Project. /// /// Config File Path public string GetConfigFilePath() { var webConfig = WebConfigManager.LoadWebConfigAsXDocument(_projectPath); var appConfig = WebConfigManager.LoadAppConfigAsXDocument(_projectPath); if (webConfig.ContainsElement(Constants.SystemServiceModelElementPath)) { return Path.Combine(_projectPath, Constants.WebConfig); } else if (appConfig.ContainsElement(Constants.SystemServiceModelElementPath)) { return Path.Combine(_projectPath, Constants.AppConfig); } else { return null; } } /// /// Given existing template Program.cs Syntax Tree, Add configurations and generate new Syntax Tree. /// /// Syntax Tree for existing Program.cs Template /// New root with updated Program.cs public SyntaxNode ReplaceProgramFile(SyntaxTree programTree) { Dictionary transportPort = new Dictionary(); Dictionary bindingModeMap = new Dictionary(); if (_projectType == ProjectType.WCFConfigBasedService) { string projectDir = _analyzerResult.ProjectResult.ProjectRootPath; bindingModeMap = GetBindingsTransportMap(projectDir); AddBinding(bindingModeMap, transportPort); } else { ProjectWorkspace project = _analyzerResult.ProjectResult; bindingModeMap = GetBindingsTransportMap(project); AddBinding(bindingModeMap, transportPort); } if(transportPort.IsNullOrEmpty()) { return programTree.GetRoot(); } var containsTransportRelatedMode = bindingModeMap.Any(b => b.Value.Mode.ToLower() == Constants.TransportMessageCredentialsMode.ToLower() || b.Value.Mode.ToLower() == Constants.TransportMode.ToLower()); var newRoot = ReplaceProgramNode(transportPort, programTree, containsTransportRelatedMode); return newRoot; } /// /// Generate Program.cs by updating Program.cs template Syntax tree. /// /// Map of Binding And Port /// Existing Program.cs Template SyntaxTree /// Flag to check if any binding uses TransportWithMessage Mode /// New root with updated Program.cs contents public static SyntaxNode ReplaceProgramNode(Dictionary transportPort, SyntaxTree programTree, bool containsTransportRelatedMode) { string httpListen = Constants.ListenLocalHostFormat; string httpsListen = Constants.ListenHttpsFormat; string netTcpMethodExpression = Constants.NetTcpFormat; var root = programTree.GetRoot(); var lambdaExpressionList = root.DescendantNodes().OfType(); if (lambdaExpressionList.IsNullOrEmpty()) { return root; } var lambdaExpression = lambdaExpressionList.First(); var block = lambdaExpression.Block; var newBlock = block; var parameter = lambdaExpression.Parameter; if (transportPort.ContainsKey(Constants.HttpProtocol)) { httpListen = String.Format(httpListen, parameter.Identifier.ValueText, transportPort.GetValueOrDefault(Constants.HttpProtocol, 8080)); newBlock = block.AddStatements(SyntaxFactory.ParseStatement(httpListen)); } if (transportPort.ContainsKey(Constants.HttpsProtocol) || containsTransportRelatedMode) { httpsListen = String.Format(httpsListen, parameter.Identifier.ValueText, transportPort.GetValueOrDefault(Constants.HttpsProtocol, 8888)); newBlock = newBlock.AddStatements(SyntaxFactory.ParseStatement(httpsListen)); } var newLambdaExpression = lambdaExpression.ReplaceNode(block, newBlock); root = root.ReplaceNode(lambdaExpression, newLambdaExpression); var memberAccessExpressions = root.DescendantNodes().OfType().ToList(); MemberAccessExpressionSyntax kestrelInvocationNode = null; foreach (MemberAccessExpressionSyntax memberAccessExpression in memberAccessExpressions) { if (memberAccessExpression.Name.Identifier.Text.Equals(Constants.UseStartupMethodIdentifier)) { kestrelInvocationNode = memberAccessExpression; break; } } if (transportPort.ContainsKey(Constants.NettcpProtocol)) { var netTCPExpression = SyntaxFactory .MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, kestrelInvocationNode.Expression, SyntaxFactory.IdentifierName(netTcpMethodExpression) ); var netTcpInvocation = SyntaxFactory.InvocationExpression(netTCPExpression, SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Argument(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(transportPort.GetValueOrDefault(Constants.NettcpProtocol, 8000)))) }))); var kestrelInvocationWithNetTcp = kestrelInvocationNode.WithExpression(netTcpInvocation); root = root.ReplaceNode(kestrelInvocationNode, kestrelInvocationWithNetTcp); } return root; } /// /// Given existing Startup.cs template file, generate new Startup.cs based on configuration. /// /// Path to existing Startup.cs template /// New Startup.cs contents public string ReplaceStartupFile(string startupFilePath) { var startupFileContents = File.ReadAllText(startupFilePath); if (_projectType == ProjectType.WCFConfigBasedService) { string configPath = Path.Combine(_projectPath, Constants.PortedConfigFileName); if (HasBehavioursTag()) { startupFileContents = HandleBehaviorsTag(startupFilePath); } return startupFileContents.Replace(Constants.XMLPathPlaceholder, "@\"" + configPath + "\""); } else { string addService = Constants.AddServiceFormat; string endpointConfigTemplate = Constants.AddServiceEndpointFormat; string endpointConfigs = ""; ProjectWorkspace project = _analyzerResult.ProjectResult; Dictionary bindingTransportMap = GetBindingsTransportMap(project); Tuple serviceInterfaceAndClass = WCFBindingAndTransportUtil.GetServiceInterfaceAndClass(project); var serviceInterfaceName = serviceInterfaceAndClass.Item1 ?? Constants.DefaultServiceInterface; var serviceClassName = serviceInterfaceAndClass.Item2 ?? Constants.DefaultServiceClass; endpointConfigs += String.Format(addService, serviceClassName); foreach(KeyValuePair keyValuePair in bindingTransportMap) { var binding = keyValuePair.Key; var mode = keyValuePair.Value.Mode; var endpointAddress = keyValuePair.Value.EndpointAddress ?? String.Join("", "\"", "/", binding.ToLower(), "\""); if (mode.ToLower() == Constants.TransportMode.ToLower()) { mode = Constants.TransportMode; } else if (mode.ToLower() == Constants.TransportMessageCredentialsMode.ToLower()) { mode = Constants.TransportMessageCredentialsMode; } if (binding == Constants.HttpProtocol) { endpointConfigs += String.Format(endpointConfigTemplate, serviceClassName, serviceInterfaceName, mode == Constants.NoneMode ? "new BasicHttpBinding()" : "new BasicHttpBinding(BasicHttpSecurityMode." + mode + ")", endpointAddress); } else if(binding == Constants.NettcpProtocol) { endpointConfigs += String.Format(endpointConfigTemplate, serviceClassName, serviceInterfaceName, mode == Constants.NoneMode ? "new NetTcpBinding()" : "new NetTcpBinding(SecurityMode." + mode + ")", endpointAddress); } else if (binding == Constants.WSHttpProtocol) { endpointConfigs += String.Format(endpointConfigTemplate, serviceClassName, serviceInterfaceName, mode == Constants.NoneMode ? "new WSHttpBinding()" : "new WSHttpBinding(SecurityMode." + mode + ")", endpointAddress); } else if (binding == Constants.HttpsProtocol) { endpointConfigs += String.Format(endpointConfigTemplate, serviceClassName, serviceInterfaceName, "new BasicHttpBinding(BasicHttpSecurityMode.Transport)", endpointAddress); } else if (binding == Constants.NethttpProtocol) { endpointConfigs += String.Format(endpointConfigTemplate, serviceClassName, serviceInterfaceName, mode == Constants.NoneMode ? "new NetHttpBinding()" : "new NetHttpBinding(BasicHttpSecurityMode." + mode + ")", endpointAddress); } } return startupFileContents.Replace(Constants.EndpointPlaceholder, endpointConfigs); } } /// /// Determines whether project config has tag /// /// Whether tag is present public bool HasBehavioursTag() { var config = WebConfigManager.LoadWebConfigAsXDocument(_projectPath); return config.ContainsElement(Constants.BehaviorsPath); } /// /// For Behaviors Tag, add Comment specifying it is unsupported in CoreWCF.s /// /// Path to existing Startup.cs /// New Startup.cs contents with Comment public static string HandleBehaviorsTag(string filePath) { var lines = File.ReadAllLines(filePath).ToList(); var newFileContents = new StringBuilder(); foreach (var line in lines) { newFileContents.AppendLine(line); if (line.Contains(Constants.WCFConfigManagerAPI)) { newFileContents .Append(Constants.WCFBehaviorsMessage); } } return newFileContents.ToString(); } public static Dictionary GetBindingsTransportMap(ProjectWorkspace project) { Dictionary bindingsTransportMap = new Dictionary(); WCFBindingAndTransportUtil.CodeBasedCheck(project, bindingsTransportMap); return bindingsTransportMap; } public static Dictionary GetBindingsTransportMap(string projectDir) { Dictionary bindingsTransportMap = new Dictionary(); WCFBindingAndTransportUtil.ConfigBasedCheck(projectDir, bindingsTransportMap); return bindingsTransportMap; } public static void AddBinding(Dictionary bindingsTransportMap, Dictionary transportPortMap) { foreach (var binding in bindingsTransportMap.Keys) { if (binding == Constants.HttpProtocol || binding == Constants.NethttpProtocol || binding == Constants.WSHttpProtocol) { transportPortMap.Add(Constants.HttpProtocol, Constants.HttpDefaultPort); } else if (binding == Constants.HttpsProtocol) { transportPortMap.Add(Constants.HttpsProtocol, Constants.HttpsDefaultPort); } else if (binding == Constants.NettcpProtocol) { transportPortMap.Add(Constants.NettcpProtocol, Constants.NetTcpDefaultPort); } } } } }