CallerMemberName parameter is blank for a specific method - c#

I have the following Log method (for a Unit Test project):
public static void WriteLogFile<T>(T obj, [System.Runtime.CompilerServices.CallerMemberName] string callingMethod = "")
{
#if WRITELOGFILE
var path = Path.Combine(LogFileLocation, callingMethod + ".json");
var content = (typeof(T) == typeof(string)) ? (obj as string) : JsonConvert.SerializeObject(obj);
File.WriteAllText(path, content);
#endif
}
And I use it in the following TestMethod:
[TestMethod]
public async Task TestGetUserInfoAsync()
{
//var tokenFile = TestUtils.GetLogFileLocation(nameof(this.TestRedeemTokensAsync2));
var tokenFile = #"D:\Temp\TestLog\TestRedeemTokensAsync2.json";
var accessToken = JsonConvert.DeserializeObject<dynamic>(File.ReadAllText(tokenFile))
.access_token.ToString();
var result = await GoogleService.GetUserInfoAsync(this.systemConfig,
accessToken);
TestUtils.WriteLogFile(result);
Assert.IsNotNull(result);
}
I have other 3 Test Methods and they all runs correctly, and they all have async/await Tasks, writing log files with the filenames taken from the method names.
However, for the above specific method, the parameter callingMethod is an empty string (not null). The result file is a file named .json (it is a valid file in Windows). I am not sure if this is related to Unit Test project only.
Why is it happening? How can I debug in this case?
Additional information: I thought it may be because I added the following method, and the 1st calling mess up:
public static string GetLogFileLocation([System.Runtime.CompilerServices.CallerMemberName] string callingMethod = "")
{
var path = Path.Combine(LogFileLocation, callingMethod + ".json");
return path;
}
However, I tried removing it and use hard string for file path (as you see in my code above) instead, but the problem persist.
EDIT: I added the Stack Trace that may be useful. I notice there is a [External Code] row between the code. I don't know what causes it.
EDIT2: The IL code indeed has problem: the 4 (sorry, 4, not 3) other TestMethods are running fine, and their IL code is correct, like this:
However, the method I am having problem with, I don't see any function call. The only WriteLogFile I can see is this one, but it is a string:
Full IL code here: http://pasted.co/cf4b0ea7

The problem seems to be happening with async and dynamic going together. Remove either one (switch to synchronous or casting into a strongly typed variable) fix the problem:
[TestMethod]
public async Task TestGetUserInfoAsync()
{
//var tokenFile = TestUtils.GetLogFileLocation(nameof(this.TestRedeemTokensAsync2));
var tokenFile = #"D:\Temp\TestLog\TestRedeemTokensAsync2.json";
var accessToken = (JsonConvert.DeserializeObject<dynamic>(File.ReadAllText(tokenFile))
.access_token.ToString()) as string; // Here, cast this to string instead of keeping it as dynamic
var result = await GoogleService.GetUserInfoAsync(this.systemConfig,
accessToken);
TestUtils.WriteLogFile(result);
Assert.IsNotNull(result);
}

Related

How can I make the CompletionService aware of other documents in the project?

I'm building an application that allows users to define, edit and execute C# scripts.
The definition consists of a method name, an array of parameter names and the method's inner code, e.g:
Name: Script1
Parameter Names: arg1, arg2
Code: return $"Arg1: {arg1}, Arg2: {arg2}";
Based on this definition the following code can be generated:
public static object Script1(object arg1, object arg2)
{
return $"Arg1: {arg1}, Arg2: {arg2}";
}
I've successfully set up an AdhocWorkspace and a Project like this:
private readonly CSharpCompilationOptions _options = new CSharpCompilationOptions(OutputKind.ConsoleApplication,
moduleName: "MyModule",
mainTypeName: "MyMainType",
scriptClassName: "MyScriptClass"
)
.WithUsings("System");
private readonly MetadataReference[] _references = {
MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
};
private void InitializeWorkspaceAndProject(out AdhocWorkspace ws, out ProjectId projectId)
{
var assemblies = new[]
{
Assembly.Load("Microsoft.CodeAnalysis"),
Assembly.Load("Microsoft.CodeAnalysis.CSharp"),
Assembly.Load("Microsoft.CodeAnalysis.Features"),
Assembly.Load("Microsoft.CodeAnalysis.CSharp.Features")
};
var partTypes = MefHostServices.DefaultAssemblies.Concat(assemblies)
.Distinct()
.SelectMany(x => x.GetTypes())
.ToArray();
var compositionContext = new ContainerConfiguration()
.WithParts(partTypes)
.CreateContainer();
var host = MefHostServices.Create(compositionContext);
ws = new AdhocWorkspace(host);
var projectInfo = ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"MyProject",
"MyProject",
LanguageNames.CSharp,
compilationOptions: _options, parseOptions: new CSharpParseOptions(LanguageVersion.CSharp7_3, DocumentationMode.None, SourceCodeKind.Script)).
WithMetadataReferences(_references);
projectId = ws.AddProject(projectInfo).Id;
}
And I can create documents like this:
var document = _workspace.AddDocument(_projectId, "MyFile.cs", SourceText.From(code)).WithSourceCodeKind(SourceCodeKind.Script);
For each script the user defines, I'm currently creating a separate Document.
Executing the code works as well, using the following methods:
First, to compile all documents:
public async Task<Compilation> GetCompilations(params Document[] documents)
{
var treeTasks = documents.Select(async (d) => await d.GetSyntaxTreeAsync());
var trees = await Task.WhenAll(treeTasks);
return CSharpCompilation.Create("MyAssembly", trees, _references, _options);
}
Then, to create an assembly out of the compilation:
public Assembly GetAssembly(Compilation compilation)
{
try
{
using (MemoryStream ms = new MemoryStream())
{
var emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
foreach (Diagnostic diagnostic in emitResult.Diagnostics)
{
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
}
else
{
ms.Seek(0, SeekOrigin.Begin);
var buffer = ms.GetBuffer();
var assembly = Assembly.Load(buffer);
return assembly;
}
return null;
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
And, finally, to execute the script:
public async Task<object> Execute(string method, object[] params)
{
var compilation = await GetCompilations(_documents);
var a = GetAssembly(compilation);
try
{
Type t = a.GetTypes().First();
var res = t.GetMethod(method)?.Invoke(null, params);
return res;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
So far, so good. This allows users to define scripts that can all each other.
For editing I would like to offer code completion and am currently doing this:
public async Task<CompletionList> GetCompletionList(Document doc, string code, int offset)
{
var newDoc = doc.WithText(SourceText.From(code));
_workspace.TryApplyChanges(newDoc.Project.Solution);
var completionService = CompletionService.GetService(newDoc);
return await completionService.GetCompletionsAsync(newDoc, offset);
}
NOTE: The code snippet above was updated to fix the error Jason mentioned in his answer regarding the use of doc and document. That was, indeed, due to the fact that the code shown here was extracted (and thereby modified) from my actual application code. You can find the original erroneous snippet I posted in his answer and, also, further below a new version which addresses the actual issue that was causing my problems.
The problem now is that GetCompletionsAsync is only aware of definitions within the same Document and the references used when creating the workspace and project, but it apparently does not have any reference to the other documents within the same project. So the CompletionList does not contain symbols for the other user scripts.
This seems strange, because in a "live" Visual Studio project, of course, all files within a project are aware of each other.
What am I missing? Are the project and/or workspace set up incorrectly? Is there another way of calling the CompletionService? Are the generated document codes missing something, like a common namespace?
My last resort would be to merge all methods generated from users' script definitions into one file - is there another way?
FYI, here are a few useful links that helped me get this far:
https://www.strathweb.com/2018/12/using-roslyn-c-completion-service-programmatically/
Roslyn throws The language 'C#' is not supported
Roslyn service is null
Updating AdHocWorkspace is slow
Roslyn: is it possible to pass variables to documents (with SourceCodeKind.Script)
UPDATE 1:
Thanks to Jason's answer I've updated the GetCompletionList method as follows:
public async Task<CompletionList> GetCompletionList(Document doc, string code, int offset)
{
var docId = doc.Id;
var newDoc = doc.WithText(SourceText.From(code));
_workspace.TryApplyChanges(newDoc.Project.Solution);
var currentDoc = _workspace.CurrentSolution.GetDocument(docId);
var completionService = CompletionService.GetService(currentDoc);
return await completionService.GetCompletionsAsync(currentDoc, offset);
}
As Jason pointed out, the cardinal error was not fully taking the immutability of the project and it's documents into account. The Document instance I need for calling CompletionService.GetService(doc) must be the actual instance contained in the current solution - and not the instance created by doc.WithText(...), because that instance has no knowledge of anything.
By storing the DocumentId of the original instance and using it to retrieve the updated instance within the solution, currentDoc, after applying the changes, the completion service can (as in "live" solutions) reference the other documents.
UPDATE 2: In my original question the code snippets used SourceCodeKind.Regular, but - at least in this case - it must be SourceCodeKind.Script, because otherwise the compiler will complain that top-level static methods are not allowed (when using C# 7.3). I've now updated the post.
So one thing looks a bit fishy here:
public async Task<CompletionList> GetCompletionList(Document doc, string code, int offset)
{
var newDoc = document.WithText(SourceText.From(code));
_workspace.TryApplyChanges(newDoc.Project.Solution);
var completionService = CompletionService.GetService(newDoc);
return await completionService.GetCompletionsAsync(document, offset);
}
(Note: your parameter name is "doc" but you're using "document" so I'm guessing this code is something you pared down from the full example. But just wanted to call that out since you might have introduced errors while doing that.)
So main fishy bit: Roslyn Documents are snapshots; a document is a pointer within the entire snapshot of the entire solution. Your "newDoc" is a new document with the text that you've substituted, and you're updating thew workspace to contain that. You're however still handing in the original document to GetCompletionsAsync, which means you're still asking for the old document in that case, which might have stale code. Furthermore, because it's all a snapshot, the changes made to the main workspace by calling TryApplyChanges won't in any way be reflected in your new document objects. So what I'm guessing might be happening here is you're passing in a Document object that doesn't actually have all the text documents updated at once, but most of them are still empty or something similar.

Variable cannot be accessed below a certain point in the code

I have a small problem with a piece of code I cannot seem to work out what the issue is, I never wrote this initial part of the code which is why I don't understand the problem.
Code:
public async Task GetEnumerableLinksAsync(string entryPointUrl)
{
Uri baseUrl = new Uri(entryPointUrl);
await foreach (Uri url in GetEnumerableLinksAsync(baseUrl, 1000))
ListBoxMain.Items.Add(url);
// TEST
string[] threads = File.ReadAllLines(#"logic\markers.txt");
if (threads.Any(url.Contains)) {
}
// TEST
}
The problem is here if (threads.Any(url.Contains)) { I cannot access the url variable.
ListBoxMain.Items.Add(url); the listbox can access it fine, but any code below this and I get the name `url` does not exist in the current context
Can anyone see the issue at all? any help is appreciated.
The code you have written looks correct because of indentation, but C# ignores any formatting.
This is your code formatted properly:
await foreach (Uri url in GetEnumerableLinksAsync(baseUrl, 1000))
ListBoxMain.Items.Add(url);
// TEST
string[] threads = File.ReadAllLines(#"logic\markers.txt");
if (threads.Any(url.Contains)) {
}
// TEST
Can you see the why url doesn't exist after the foreach now?
You need to add a 'scope' to the foreach to allow url to exist beyond the first line:
await foreach (Uri url in GetEnumerableLinksAsync(baseUrl, 1000))
{
ListBoxMain.Items.Add(url);
// TEST
string[] threads = File.ReadAllLines(#"logic\markers.txt");
if (threads.Any(url.Contains)) {
}
// TEST
}
Two hints:
start using Ctrl+KD more often. This formats your code and would have shown the issue.
Always use braces when using compound functions, like if,else,foreach etc.

Make function more Generic to save repeating

I use the following function which is all well and fine but i basically do the same operation about 20 times. For various end points of an api I am hitting how would one make this routing more Generic in the ability to pass and return type OF T.
public async Task<List<StockItem>> GetStockDataFromSage()
{
StockItem stockitems = new StockItem();
string content = "";
List<StockItem> result = new List<StockItem>();
var uri = new Uri(string.Format(Constants.GetStockItems, string.Empty));
var response = await _client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
content = await response.Content.ReadAsStringAsync();
result = Newtonsoft.Json.JsonConvert.DeserializeObject<List<StockItem>>(content);
}
return result;
}
Edit 1
I am trying to use the below however I am getting an error
public async Task<List<StockItem>> GetStockItemInfo()
{
return await dataTransfer.GetDataFromSageService(Constants.GetStockItems, string.Empty)) ?? new List<StockItem>();
}
Severity Code Description Project File Line Suppression State
Error CS1061 'StockTakeDT' does not contain a definition for 'GetStockDataFromSage' and no accessible extension method 'GetStockDataFromSage' accepting a first argument of type 'StockTakeDT' could be found (are you missing a using directive or an assembly reference?) StockAppDL D:\Git\Repos\StockApp\FStockApp\StockAppDal\StockDatabase.cs 76 Active
Your objective here appears to be to call an endpoint and get the results back into an object you can use. If the call is successful, you return the result and if it fails, you return an empty list.
We can abstract that logic out into a generic method that accepts a url and parameters and returns an object.
public async Task<T> GetObjectFromEndpoint<T>(string url, params string[] args)
where T : class
{
var uri = new Uri(string.Format(url, args));
var response = await _client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(content);
}
return default(T);
}
Now your GetStockDataFromSage function passes in the information unique to this call, namely the url, parameters, and generic type for the results. If the result is null, GetStockDataFromSage returns an empty list of StockItems
public async Task<List<StockItem>> GetStockDataFromSage()
{
return (await GetObjectFromEndpoint<List<StockItem>>(Constants.GetStockItems, string.Empty)) ?? new List<StockItem>();
}
Any time you are trying to minimize repetition, you want to look at what is specific to this call and what is more general. i.e List<StockItem>, the url, and possibly the parameter are unique, but the rest of the code is very general.
Caution: This method of returning a default value when the api call fails can lead to hard-to-diagnose issues where you will be unable to differentiate between an empty list and a failed api call. I recommend adding some logging for failed api calls and perhaps looking at ways to inform the calling code that the result was in error.

Async unit testing with NUnit and C#

I have the following method I created it's nothing fancy just retrieves data from an HTTP server but it is an async method.
public async Task<string> GetStringFromConsul(string key)
{
string s = "";
// attempts to get a string from Consul
try
{
//async method to get the response
HttpResponseMessage response = await this.http.GetAsync(apiPrefix + key);
//if it responds successfully
if (response.IsSuccessStatusCode)
{
//parse out a string and decode said string
s = await response.Content.ReadAsStringAsync();
var obj = JsonConvert.DeserializeObject<List<consulValue>>(s);
s = Encoding.UTF8.GetString(Convert.FromBase64String(obj[0].value));
}
else
{
s = requestErrorCodePrefix + response.StatusCode + ">";
}
}
catch(Exception e)
{
//need to do something with the exception
s = requestExceptionPrefix + e.ToString() + ">";
}
return s;
}
Then in the test I call the code just like I do during normal execution:
[Test]
public async Task GetStringFromConsulTest()
{
ConsulConfiguration cc = new ConsulConfiguration();
string a = cc.GetStringFromConsul("").GetAwaiter().GetResult();
Assert.AreEqual(a, "");
}
However I get an exception like so instead of any sort of string:
Message: Expected string length 514 but was 0. Strings differ at index 0.
Expected: "<Request Exception: System.Threading.Tasks.TaskCanceledExcept..."
But was: <string.Empty>
I've looked around and found a few tutorials on this and tried it but to no avail. If anyone can point me in the right direction I would appreciate it, I'm pretty new to C# unit testing.
I'm a stickler for good error messages so I'd first change the assert to
Assert.AreEqual("", a);
because the first argument is your expected value. Now it will fail with
Message: Expected string length 0 but was 514. Strings differ at index 0.
Expected: <string.Empty>
But was: "<Request Exception: System.Threading.Tasks.TaskCanceledExcept..."
...still a failure, but a much more sensible message.
Next, to pass, add an await to your async method call, as suggested by M Hassan.
In Nunit Framework, Use async/await in unit test as in the following:
[Test]
public async Task GetStringFromConsulTest()
{
ConsulConfiguration cc = new ConsulConfiguration();
//string a = cc.GetStringFromConsul("").GetAwaiter().GetResult();
//use await instead
string a = await cc.GetStringFromConsul("");
Assert.AreEqual(a, "");
}
For more details, read Async Support in NUnit
It's better to test your method in case of firing exceptions NUnit expected exceptions
Update:
The comment:
I still get the error even when structuring the method like this.
That error means that the test fail and there is a bug in the source code method GetStringFromConsul.
Your test method include the Assert statement:
Assert.AreEqual(a, "");
That means that you expect a variable which is calculated from a=cc.GetStringFromConsul("") should be "" to pass,
otherwise the test fail and NUnit Framework Fire an exception like:
Message: Expected string length 514 but was 0. Strings differ at index 0.
Expected: "<Request Exception: System.Threading.Tasks.TaskCanceledExcept..."
But was: <string.Empty>
To resolve this exception, you should resolve the bug in the method GetStringFromConsul to return "" when the input parameter=""
Maybe this.http.GetAsync(apiPrefix + key); is timing out. That would give you a TaskCanceledException. Not sure what your value of apiPrefix is.

HttpClient ReadAsAsync does not deserialize

I am not sure if the issue I am having is related to the way I'm using Task or if I am an not using ReadAsAsync correctly. I am following the pattern I found here:
http://blogs.msdn.com/b/henrikn/archive/2012/02/11/httpclient-is-here.aspx
Background:
Object I am deserializing is a POCO. Properties have no attributes. It is just a few value type properties and a couple collection properties. REST service appears to work ok also. When I look at the JSON returned by the service it appears to be OK.
Using Web API 2.1 5.1.2
Problem:
.. is calling HttpResponseMessage.Content.ReadAsAsync(). Sometimes it works (returns an object) and sometimes it doesn't (throws "Thread was being aborted" or returns null). It appears the content property can be read once only and subsequent reads throw errors. See comments in code below.
Related questions:
HttpContent.ReadAsAsync Deserialization issue
Question appears to be similar to mine. Answer indicates a bug but this is over two years old.
Code:
[TestMethod]
public void AddSiteTest()
{
// Use POST to create a resource i.e. insert. Use PUT to update.
Site site = new Site {SiteName = "Test", Active = true, URI="www.test.com" };
Site newSite = null;
client.PostAsJsonAsync<Site>(baseURI + "/Sites/AddSite?securityKey="+ SecurityKey, site).ContinueWith(x =>
{
HttpResponseMessage response = x.Result;
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
try
{
string str = Task.Run(() => response.Content.ReadAsStringAsync()).Result; // yep its json and it is a proprety serialized object
// Method 1 (preferred... ):
//Site siteA = Task.Run(() => response.Content.ReadAsAsync<Site>()).Result; // usuallly throws if content has been read
// Method 2:
Site siteB = response.Content.ReadAsAsync<Site>().Result; // usully returns a valid result (when I dont put a breakpoint on it). Does not deadlock.
// Method 3:
response.Content.ReadAsAsync<Site>().ContinueWith(d =>
{
Site siteC = d.Result; // returns null
});
}
catch (Exception ex)
{
string y = ex.Message;
}
}
});
}
try to use await:
string str = await response.Content.ReadAsStringAsync();
And you have to add async before void in your method.

Categories