Create an "all-in-one" function using DS in C# - c#

In my web app I use several asmx (Web Services) from the same provider, they have one for this, other for that, but all require a SOAP Header with Authentication.
It is simple to add the Authentication:
public static SoCredentialsHeader AttachCredentialHeader()
{
SoCredentialsHeader ch = new SoCredentialsHeader();
ch.AuthenticationType = SoAuthenticationType.CRM5;
ch.UserId = "myUsername";
ch.Secret = apUtilities.CalculateCredentialsSecret(
SoAuthenticationType.CRM5, apUtilities.GetDays(), "myUsername", "myPassword");
return ch;
}
The problem is this SoCredentialsHeader come (derivation) from ONE webservice and I need to add the same code to the others, like:
public static wsContact.SoCredentialsHeader AttachContactCredentialHeader()
{
wsContact.SoCredentialsHeader ch = new wsContact.SoCredentialsHeader();
ch.AuthenticationType = wsContact.SoAuthenticationType.CRM5;
ch.UserId = "myUsername";
ch.Secret = apUtilities.CalculateCredentialsSecret(
wsContact.SoAuthenticationType.CRM5, apUtilities.GetDays(), "myUsername", "myPassword");
return ch;
}
public static wsDiary.SoCredentialsHeader AttachDiaryCredentialHeader()
{
wsDiary.SoCredentialsHeader ch = new wsDiary.SoCredentialsHeader();
ch.AuthenticationType = wsDiary.SoAuthenticationType.CRM5;
ch.UserId = "myUsername";
ch.Secret = apUtilities.CalculateCredentialsSecret(
wsDiary.SoAuthenticationType.CRM5, apUtilities.GetDays(), "myUsername", "myPassword");
return ch;
}
Is there a way to implement a design pattern in order to use only one function but that suits all webServices?
sometimes I see the T letter, is this a case for that? if yes, how can I accomplish such feature?
P.S. I could pass an enum and use a switch to check the enum name and apply the correct Header but everytime I need to add a new WebService, I need to add the enum and the code, I'm searcging for an advanced technique for this.
Thank you.

Create a file called whatever.tt (the trick is the .tt extension) anywhere in your VS solution and paste the following code:
using System;
namespace Whatever
{
public static class Howdy
{
<#
string[] webServices = new string[] {"wsContact", "wsDiary"};
foreach (string wsName in webServices)
{
#>
public static <#=wsName#>.SoCredentialsHeader AttachContactCredentialHeader()
{
<#=wsName#>.SoCredentialsHeader ch = new <#=wsName#>.SoCredentialsHeader();
ch.AuthenticationType = <#=wsName#>.SoAuthenticationType.CRM5;
ch.UserId = "myUsername";
ch.Secret = apUtilities.CalculateCredentialsSecret(<#=wsName#>.SoAuthenticationType.CRM5,
apUtilities.GetDays(), "myUsername", "myPassword");
return ch;
}
}
<# } #>
}
Then watch as a whatever.cs magically appears with the desired code snippets.These are called T4 templates for code generation in VS.
You'll want to turn these into partial classes or extension methods or something. The above code will not function "as is" but you get the idea.

I don't know if this is something you wish to consider, as it definitely has it's down sides, but - as ultimately these ( the varoius SoCredentialsHeader classes) are all copies of the same class definition in different namespaces so with a little bit of refactoring you could simply have one class and one method.
Copy the SoCredentialsHeader class definition to a project of your own, add a reference to it and remove the class definition from all web service's proxies.
Add a using statement at the top of the proxy code file and it would not tell the difference.
Basically you told it to use the same class definition (yours) for all web services.
The obvious down side is that you have to repeat this exercise whenever you update and web service reference (and that it assumes all services keep using the same definition), but we've been doing this in a similar scenario and it worked quite well for us.

I would try using a generic method, then use reflection to set the properties:
public static T AttachDiaryCredentialHeader<T>() where T: class
{
T ch = new T();
Type objType = ch.GetType();
PropertyInfo userId = objType.GetProperty("UserId");
authType.SetValue(ch, "myUsername", null)
//And so on for the other properties...
return ch;
}
IMHO, This is somewhat hacky, I would keep them separate, unless like the previous post mentioned it, you're absolutely sure the definitions of these services will remain the same. One minor change to one of them will break this.

Related

Roslyn add new method to an existing class

I'm investigating the use of the Roslyn compiler within a Visual Studio Extension (VSIX) that uses the VisualStudioWorkspace to update existing code. Having spent the last few days reading up on this, there seem to be several ways to achieve this....I'm just not sure which is the best approach for me.
Okay, so let's assume that the User has their solution open in Visual Studio 2015. They click on my Extension and (via a form) they tell me that they want to add the following method definition to an interface:
GetSomeDataResponse GetSomeData(GetSomeDataRequest request);
They also tell me the name of the interface, it's ITheInterface.
The interface already has some code in it:
namespace TheProjectName.Interfaces
{
using System;
public interface ITheInterface
{
/// <summary>
/// A lonely method.
/// </summary>
LonelyMethodResponse LonelyMethod(LonelyMethodRequest request);
}
}
Okay, so I can load the Interface Document using the following:
Document myInterface = this.Workspace.CurrentSolution?.Projects?
.FirstOrDefault(p
=> p.Name.Equals("TheProjectName"))
?.Documents?
.FirstOrDefault(d
=> d.Name.Equals("ITheInterface.cs"));
So, what is the best way to now add my new method to this existing interface, ideally writing in the XML comment (triple-slash comment) too? Bear in mind that the request and response types (GetSomeDataRequest and GetSomeDataResponse) may not actually exist yet. I'm very new to this, so if you can provide code examples then that would be terrific.
UPDATE
I decided that (probably) the best approach would be simply to inject in some text, rather than try to programmatically build up the method declaration.
I tried the following, but ended up with an exception that I don't comprehend:
SourceText sourceText = await myInterface.GetTextAsync();
string text = sourceText.ToString();
var sb = new StringBuilder();
// I want to all the text up to and including the last
// method, but without the closing "}" for the interface and the namespace
sb.Append(text.Substring(0, text.LastIndexOf("}", text.LastIndexOf("}") - 1)));
// Now add my method and close the interface and namespace.
sb.AppendLine("GetSomeDataResponse GetSomeData(GetSomeDataRequest request);");
sb.AppendLine("}");
sb.AppendLine("}");
Inspecting this, it's all good (my real code adds formatting and XML comments, but removed that for clarity).
So, knowing that these are immutable, I tried to save it as follows:
var updatedSourceText = SourceText.From(sb.ToString());
var newInterfaceDocument = myInterface.WithText(updatedSourceText);
var newProject = newInterfaceDocument.Project;
var newSolution = newProject.Solution;
this.Workspace.TryApplyChanges(newSolution);
But this created the following exception:
bufferAdapter is not a VsTextDocData
at Microsoft.VisualStudio.Editor.Implementation.VsEditorAdaptersFactoryService.GetAdapter(IVsTextBuffer bufferAdapter)
at Microsoft.VisualStudio.Editor.Implementation.VsEditorAdaptersFactoryService.GetDocumentBuffer(IVsTextBuffer bufferAdapter)
at Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.InvisibleEditor..ctor(IServiceProvider serviceProvider, String filePath, Boolean needsSave, Boolean needsUndoDisabled)
at Microsoft.VisualStudio.LanguageServices.RoslynVisualStudioWorkspace.OpenInvisibleEditor(IVisualStudioHostDocument hostDocument)
at Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.DocumentProvider.StandardTextDocument.UpdateText(SourceText newText)
at Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl.ApplyDocumentTextChanged(DocumentId documentId, SourceText newText)
at Microsoft.CodeAnalysis.Workspace.ApplyProjectChanges(ProjectChanges projectChanges)
at Microsoft.CodeAnalysis.Workspace.TryApplyChanges(Solution newSolution)
at Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl.TryApplyChanges(Solution newSolution)
If I were you I would take advantage of all Roslyn benefits, i.e. I would work with the SyntaxTree of the Document rather than processing the files text (you are able to do the latter without using Roslyn at all).
For instance:
...
SyntaxNode root = await document.GetSyntaxRootAsync().ConfigureAwait(false);
var interfaceDeclaration = root.DescendantNodes(node => node.IsKind(SyntaxKind.InterfaceDeclaration)).FirstOrDefault() as InterfaceDeclarationSyntax;
if (interfaceDeclaration == null) return;
var methodToInsert= GetMethodDeclarationSyntax(returnTypeName: "GetSomeDataResponse ",
methodName: "GetSomeData",
parameterTypes: new[] { "GetSomeDataRequest" },
paramterNames: new[] { "request" });
var newInterfaceDeclaration = interfaceDeclaration.AddMembers(methodToInsert);
var newRoot = root.ReplaceNode(interfaceDeclaration, newInterfaceDeclaration);
// this will format all nodes that have Formatter.Annotation
newRoot = Formatter.Format(newRoot, Formatter.Annotation, workspace);
workspace.TryApplyChanges(document.WithSyntaxRoot(newRoot).Project.Solution);
...
public MethodDeclarationSyntax GetMethodDeclarationSyntax(string returnTypeName, string methodName, string[] parameterTypes, string[] paramterNames)
{
var parameterList = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(GetParametersList(parameterTypes, paramterNames)));
return SyntaxFactory.MethodDeclaration(attributeLists: SyntaxFactory.List<AttributeListSyntax>(),
modifiers: SyntaxFactory.TokenList(),
returnType: SyntaxFactory.ParseTypeName(returnTypeName),
explicitInterfaceSpecifier: null,
identifier: SyntaxFactory.Identifier(methodName),
typeParameterList: null,
parameterList: parameterList,
constraintClauses: SyntaxFactory.List<TypeParameterConstraintClauseSyntax>(),
body: null,
semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken))
// Annotate that this node should be formatted
.WithAdditionalAnnotations(Formatter.Annotation);
}
private IEnumerable<ParameterSyntax> GetParametersList(string[] parameterTypes, string[] paramterNames)
{
for (int i = 0; i < parameterTypes.Length; i++)
{
yield return SyntaxFactory.Parameter(attributeLists: SyntaxFactory.List<AttributeListSyntax>(),
modifiers: SyntaxFactory.TokenList(),
type: SyntaxFactory.ParseTypeName(parameterTypes[i]),
identifier: SyntaxFactory.Identifier(paramterNames[i]),
#default: null);
}
}
Note that this is pretty raw code, Roslyn API is extremely powerful when it comes to analyzing/processing the syntax tree, getting symbol information/references and so on. I would recommend you to look at this page and this page for reference.

C# and IronPython integration

I want simply use io.py from C# to write a file and I use the following code:
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;
...
System.IO.Directory.SetCurrentDirectory(
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) +
"\IronPython\Lib");
ScriptRuntime py = Python.CreateRuntime();
dynamic io = py.UseFile("io.py");
dynamic f = io.open("tmp.txt", "w");
f.writelines("some text...");
f.close();
but when I run the program the runtime give me a:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException telling that no overload of writelines accept argument '1'
it seems like that method doesn't exists... but into io.py documentation it exists.
P.S. The same is for close method!!!
Any idea?
I can only tell you how to make your code working, however I don't have much experience with IronPython and have no idea why it is done this way (though I try to learn that). First, it seems io module is treated in a special way and there is special (non-dynamic) class for that. When you do io.open what is returned is instance of PythonIOModule._IOBase class. You can do
var f = (PythonIOModule._IOBase) io.open("tmp.txt", "w");
And see for yourself that "writeline" method (which is regular method, not a dynamic one) accepts CodeContext instance as first argument, and second argument is lines. Interesting that this class itself already contains field with that CodeContext, but it is made internal for some reason, and what is even worse - writelines (and other methods) could have been using that CodeContext and not require us to provide external one. Why is it done like this - I have no idea.
So to make your code working, we have to get CodeContext somewhere. One way is do that via reflection:
var context = (CodeContext) f.GetType().GetField("context", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(f);
Another way is to craft it yourself:
var languageContext = HostingHelpers.GetLanguageContext(engine);
var context = new ModuleContext(io._io, new PythonContext(languageContext.DomainManager, new Dictionary<string, object>())).GlobalContext;
Both methods will work and program will successfully write to a file. Full working sample:
static void Main(string[] args) {
System.IO.Directory.SetCurrentDirectory(#"G:\Python27\Lib");
var engine = Python.CreateEngine();
dynamic io = engine.ImportModule("io");
var f = (PythonIOModule._IOBase) io.open("tmp.txt", "w");
var languageContext = HostingHelpers.GetLanguageContext(engine);
var context = new ModuleContext(io._io, new PythonContext(languageContext.DomainManager, new Dictionary<string, object>())).GlobalContext;
f.writelines(context, "some text....");
f.close(context);
}

Auto-generated code needs to make static class

I decided to help my friend with a project he's working on. I'm trying to write a test webpage for him to verify some new functionality, but in my auto-generated code I get
CS1106: Extension method must be defined in a non-generic static class
Implementing the code in index.cshtml isn't the best way to do this, but we are just trying to do a proof of concept and will do a proper implementation later.
In all the places I looked they pretty much said that all the functions I define must be in a static class (as the error states). That wouldn't be so bad except for the class that holds all my functions is auto-generated and not static. I'm not really sure what settings I can change to fix this.
Here is a copy of the relevant (I believe) parts of code. The implementation of some or all of the functions may be incorrect. I haven't tested them yet
#{
HttpRequest req = System.Web.HttpContext.Current.Request;
HttpResponse resp = System.Web.HttpContext.Current.Response;
var url = req.QueryString["url"];
//1 Download web data from URL
//2 Write the final edited version of the document to the response object using resp.write(String x);
//3 Add Script tag for dom-outline-1.0 to html agility pack document
//4 Search for relative URLs and correct them to become absolute URL's that point back to the hostname
}
#functions
{
public static void PrintNodes(this HtmlAgilityPack.HtmlNode tag)
{
HttpResponse resp = System.Web.HttpContext.Current.Response;
resp.Write(tag.Name + tag.InnerHtml);
if (!tag.HasChildNodes)
{
return;
}
PrintNodes(tag.FirstChild);
}
public static void AddScriptNode(this HtmlAgilityPack.HtmlNode headNode, HtmlAgilityPack.HtmlDocument htmlDoc, string filePath)
{
string content = "";
using (StreamReader rdr = File.OpenText(filePath))
{
content = rdr.ReadToEnd();
}
if (headNode != null)
{
HtmlAgilityPack.HtmlNode scripts = htmlDoc.CreateElement("script");
scripts.Attributes.Add("type", "text/javascript");
scripts.AppendChild(htmlDoc.CreateComment("\n" + content + "\n"));
headNode.AppendChild(scripts);
}
}
}
<HTML CODE HERE>
If you were really smart you would encapsulate the design to take Delegates, reason being if you use a delegate you don't have to worry about referencing something static.
public delegate void MyUrlThing(string url, object optional = null);
Possibly some state...
public enum UrlState
{
None,
Good,
Bad
}
Then void would become UrlState...
Also if you wanted you could also setup a text box and blindly give it CIL....
Then you would compile the delegates using something like this
http://www.codeproject.com/Articles/578116/Complete-Managed-Media-Aggregation-Part-III-Quantu
This way you can use also then optionally just use the IL to augment whatever you wanted.
You could also give it CSharp code I suppose...
If you want to keep you design you can also then optionally use interfaces... and then put the compiled dll in a directory and then load it etc... as traditionally

NRefactory: How do I access unresolved Named Arguments on a Property Attribute?

I apologize in advance for the long description of a simple question but I want to make sure people properly understand what I'm trying to do.
Background
I'm writing a tool that can read in a file generated by SqlMetal and create a class that contains methods for simple Inserting, Updating, Deleting and Selecting, which can then be exposed to a web service. The main advantage here is that if a table changes, I simply have to re-run the tool and the database-related code is automatically updated and everywhere that uses it will generate compile errors, making it easy to track down where manual changes need to be made. For example, if I have a Customer table that has the following fields:
CustomerId (PK, Identity)
FirstName
LastName
I want to be able to generate Insert and Delete methods as follows:
// I only want non-database-generated fields to be parameters here.
public static Customer InsertCustomer(String firstName, String lastName)
{
...
}
// I only want the primary key fields to be parameters here.
public static int DeleteCustomer(int customerId)
{
...
}
I am using SqlMetal to generate a Customer class. Now what I want to do is read that .cs file into my new tool in order to create another class with the above methods. This new class can then be exposed to the web service to grant access to this functionality without having to expose the underlying database. I am using NRefactory to read in the SqlMetal-generated file and so far, it's going well but I've run into a snag trying to read the property attributes on my Customer class.
SqlMetal generates its classes using a ColumnAttribute to identify each property that is derived from a database column. The ColumnAttribute will have a number of arguments to describe the database column's properties. In the above example, it would generate something like this:
...
[Column(Name="customerId", Storage="_CustomerId, DbType="INT NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
public int CustomerId
{
...
}
[Column(Name="firstName", Storage="_FirstName", DbType="NVarChar(100) NOT NULL", CanBeNull=false)]
public String FirstName
{
...
}
[Column(Name="lastName", Storage="_LastName", DbType="NVarChar(100) NOT NULL", CanBeNull=false)]
public String LastName
{
...
}
...
Problem
As you can see, SqlMetal gives me the attributes I need in order to identify which columns are database-generated and which ones are part of the primary key. So when I read this file into NRefactory and resolve the type, I would expect to be able to get at all of this information. However, I'm finding that while I can get to the ColumnAttribute, all of the arguments on it are unresolved and therefore aren't accessible via the NamedArguments or PositionalArguments properties.
Here's my code:
SyntaxTree syntaxTree = ...;
foreach(AstNode tableNode in syntaxTree.Children)
{
ResolveResult result = resolver.Resolve(tableNode);
var properties = result.Type.GetProperties();
foreach (IProperty p in properties)
{
var attributes = p.Attributes;
bool isPrimaryKeyField = false;
bool isDbGenerated = false;
bool isColumn = false;
foreach (IAttribute attr in attributes)
{
if (attr.AttributeType.Name == "Column")
{
isColumn = true;
foreach (var arg in attr.NamedArguments) // NamedArguments contains no items.
{
if (arg.Key.Name == "IsPrimaryKey")
{
isPrimaryKeyField = (bool)arg.Value.ConstantValue == true;
}
if (arg.Key.Name == "IsDbGenerated")
{
isDbGenerated = (bool)arg.Value.ConstantValue == true;
}
}
}
}
if (isColumn)
{
... // Create a parameter as appropriate.
}
}
}
This all works until I try to loop through the IAttribute.NamedArguments because the collection contains no elements. However, when I go through the debugger and examine the value of 'attr', I can see that there is a private variable called 'unresolved', which contains a list of all the arguments I want but I can find no way to access this through code.
How do I get at the contents of this 'unresolved' variable? Do I need to do something more with the Resolver? This is my first time using NRefactory so I'm not overly familiar with all the nuances yet. I've been having a tough time finding an example that goes into this level of depth on Google and the documentation I've seen for NRefactory doesn't seem to cover it. Any help would be appreciated.
I figured it out. I needed to load the assembly for System.Data.Linq into the IProjectContent before resolving the SyntaxTree.
...
CecilLoader loader = new CecilLoader();
Assembly[] assembliesToLoad = {
...
typeof(System.Data.Linq.Mapping.ColumnAttribute).Assembly
...};
IUnresolvedAssembly[] projectAssemblies = new IUnresolvedAssembly[assembliesToLoad.Length];
for(int i = 0; i < assembliesToLoad.Length; i++)
{
projectAssemblies[i] = loader.LoadAssemblyFile(assembliesToLoad[i].Location);
}
IProjectContent project = new CSharpProjectContent();
project = project.AddAssemblyReferences(projectAssemblies);
...

How to change an EF connection string, based on subdomain of MVC web site?

I have an EF5 ASP.NET MVC 3 (Razor) web site, running under IIS7. Now I want to be able to change the connection string to the MSSQL database depending on the subdomain of the URL, e.g. foo.mydomain.com should connect to my "Foo" database, and bar.mydomain.com should connect to the "Bar" database.
Obviously the DNS records are set up so that they all point to the same web site.
What's the most efficient way of achieving this?
why don't you start passing your own SqlConnection to your YourDbContext?
var partialConString = ConfigurationManager.ConnectionStrings["DBConnectionStringName"].ConnectionString;
var connection = new SqlConnection("Initial Catalog=" + Request.Url.Host + ";" + partialConString);
var context = new MyDbContext(connection, true);
You can also change database in the DBContext:
context.Database.Connection.ChangeDatabase("newDbname");
It's not very easy...
You should change the constructor of object context to dynamically change the connection string.
Take the subdomain name using System.Web.HttpContext.Current.Request.Url.Host. Then use it to compute the proper connection string.
You should do this in the designer generated code. Of course this is not a good place.. to make it work use the T4 templating. Open your model and right click on the blank designer surface, then select "Add code generation item" -> Ado.net entity object generation. This will create a .tt file. Open it and look for the constructor syntax. Add your logic there.
Good luck!
I've come up with what I feel is a better solution than all those proposed to date. I'm using the default EntityModelCodeGenerator, so perhaps there are other, better, solutions for other templates - but this works for me:
Create the other half of the partial class MyEntities.
Override OnContextCreated(), which is called from within the class constructor.
Change the store connection string using a regex.
This comes out as follows:
partial void OnContextCreated()
{
// change connection string, depending on subdomain
if (HttpContext.Current == null) return;
var host = HttpContext.Current.Request.Url.Host;
var subdomain = host.Split('.')[0];
switch (subdomain)
{
case "foo":
ChangeDB("Foo");
break;
case "bar":
ChangeDB("Bar");
break;
}
}
private void ChangeDB(string dbName)
{
var ec = Connection as EntityConnection;
if (ec == null) return;
var match = Regex.Match(ec.StoreConnection.ConnectionString, #"Initial Catalog\s*=.*?;", RegexOptions.IgnoreCase);
if (!match.Success) return;
var newDbString = "initial catalog={0};".Fmt(dbName);
ec.StoreConnection.ConnectionString = ec.StoreConnection.ConnectionString.Replace(match.Value, newDbString);
}
Either use different connection strings in the web.config. Maybe research a bit if you can have conditional XSL transformations, that way, when you publish on a specific configuration the web.Release.config will change your Web.Config to be what you need it to be.
Or, use |DataDirectory| string substitution - http://msdn.microsoft.com/en-us/library/cc716756.aspx
more on DataDirectory string substitution here:
http://forums.asp.net/t/1835930.aspx/1?Problem+With+Database+Connection
I guess, if you want to be by the book, create build configurations for each of your separate releases and put the connection string in the respective web..config and when you publish, that XSL transformation will put the connection string in the resulting web.config and voila.
I've done something like that recently by adding some custom configuration, which uses the host header to determine the connectionStringName, which has to be used.
EF5 has a constructor, which can handle this name
var context = new MyDbContex("name=<DBConnectionStringName>");
I just did for a project
public partial class admpDBcontext : DbContext
{
public static string name
{
get
{
if (System.Web.HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority).ToString() == "http://fcoutl.vogmtl.com")
{
return "name=admpDBcontext";
}
else { return "name=admpNyDBcontext"; }
}
}
public admpDBcontext()
: base(name)
{
}
}
And in the web.config I add the connectionString.

Categories