Roslyn Get method invocation references in solution - c#

I've created a very simple Solution with two projects inside it using .NET framework 4.6
TestReference.Data.dll
DataRepository.cs
using System.Collections.Generic;
using System.Linq;
namespace TestReferences.Data
{
public class DataRepository
{
public IEnumerable<string> GetProductNames()
{
return Enumerable.Repeat("Prod Name", 30);
}
public IEnumerable<int> GetProductIds()
{
return Enumerable.Range(12, 13);
}
}
}
ProductService.cs
using System.Collections.Generic;
namespace TestReferences.Data
{
public class ProductService : IProductService
{
private readonly DataRepository dataRepository;
public ProductService()
{
dataRepository = new DataRepository();
}
public IEnumerable<string> GetRecentProductNames()
{
return this.dataRepository.GetProductNames();
}
public IEnumerable<int> GetRecentProductIds()
{
return this.dataRepository.GetProductIds();
}
}
}
IProductService.cs
using System.Collections.Generic;
namespace TestReferences.Data
{
public interface IProductService
{
IEnumerable<int> GetRecentProductIds();
IEnumerable<string> GetRecentProductNames();
}
}
TestReference.Web.dll
an MVC project that has a reference to the TestReference.Data.dll
HomeController.cs
using System.Web.Mvc;
using TestReferences.Data;
namespace TestReferences.Web.Controllers
{
public class HomeController : Controller
{
IProductService productService;
public HomeController()
{
this.productService = new ProductService();
}
public ActionResult Index()
{
this.productService.GetRecentProductIds();
this.productService.GetRecentProductNames();
return View();
}
public ActionResult About()
{
this.productService.GetRecentProductNames();
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
}
In that structure, if you open ProductService.cs and place the cursor over GetRecentProductNames then press shift + F12 it shows 2 results in TestReference.Web (Code-lens shows one more in the interface, only for Enterprise editions ).
I've created another console application to get the same references.
public static void Main()
{
MSBuildWorkspace ms = MSBuildWorkspace.Create();
Solution solution = ms.OpenSolutionAsync(#"D:\SampleApps\TestReferences\TestReferences.sln").Result;
Document doc = solution
.Projects
.Where(p => p.Name == "TestReferences.Data")
.SelectMany(p => p.Documents)
.FirstOrDefault(d => d.Name == "ProductService.cs");
if (doc == null)
{
throw new NullReferenceException("DOc");
}
SemanticModel model = doc.GetSemanticModelAsync().Result;
List<MethodDeclarationSyntax> methodDeclarations = doc.GetSyntaxRootAsync().Result.DescendantNodes().OfType<MethodDeclarationSyntax>().ToList();
// Method declaration is GetRecentProductNames()
MethodDeclarationSyntax m = methodDeclarations.First();
ISymbol symbolInfo = model.GetDeclaredSymbol(m);
IEnumerable<ReferencedSymbol> references = SymbolFinder.FindReferencesAsync(symbolInfo, doc.Project.Solution).Result;
}
I receive two items inside the references, but their locations are 0.
[0] | GetRecentProductNames, 0 refs
[1] | GetRecentProductNames, 0 refs
It's my first touch with Roslyn and Microsoft.CodeAnalysis. The Version of the library is
<package id="Microsoft.CodeAnalysis" version="2.6.1" targetFramework="net46" />

GetDeclaredSymbol returns the associated symbol if its a declaration syntax node. This is not what you're looking for.
You should use GetSymbolInfo to get the symbol information about a syntax node:
var symbolInfo = semanticModel.GetSymbolInfo(m);
var references = SymbolFinder.FindReferencesAsync(symbolInfo, doc.Project.Solution).Result;

Actually I was trying to achieve the same functionality as View Call Hierarchy which is embedded in Visual Studio 2017. Just right click over method.

Related

What can I do to handle different requests?

Preface
I have created an ASP.NET Code Web API using Visual Studio 2022 to familiarise myself with the topic of "Web APIs".
I have converted the project to .NET 5.
In the MyControllers.cs I have so far code for a Get and for a Post request.
I have removed the WeatherForecast.cs created by Visual Studio. I have also set in the launchSettings.json that the browser jumps to MyController from the beginning.
To practice Dependency Injection, I added another project called TextRepository (a class library) to the assembly. I had written a text file with another Visual Studio project that contains the numbers from 0–99. This text file is read in and returned in the Get method of MyController.cs. Now the numbers are displayed in the browser when called. I had also included the interface INumberRepository.
To practise the whole thing again, I created another repository: ImageRepository. The aim is to find all pictures in the folder Pictures and store them in a List. To do this, I downloaded System.Drawing.Common from the NuGet package manager.
Question for you:
I am still struggling a bit to request the API in the browser for different purposes. I still want to use the call https://localhost:44355/api/My for displaying the numbers in the browser, i.e., the Get method. How can I make it so that I use a different link to transfer the image data? I am concerned with the call to the API – do I have to write a second Get function? – And about transferring the bytes of the images.
If anyone wonders what the images are: I have created 2 test images for this purpose (1920 pixels ×1080 pixels).
WebApplication2
in Startup.cs
services.AddScoped<TextRepository.INumberRepository, TextRepository.NumberTextFileRepository>();
services.AddScoped<ImageRepository.IImageTransferRepository, ImageRepository.ImageTransferRepository>();
MyController.cs
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using TextRepository;
using ImageRepository;
namespace WebApplication2.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MyController : ControllerBase
{
private readonly INumberRepository numberRepository;
private readonly IImageTransferRepository imageRepository;
public MyController(INumberRepository numberRepository, IImageTransferRepository imageTransferRepository)
{
this.numberRepository = numberRepository;
this.imageRepository = imageTransferRepository;
}
[HttpGet]
public ActionResult<List<int>> Get()
{
List<int> numbers = this.numberRepository.GetNumbers();
List<System.Drawing.Bitmap> foundImages = this.imageRepository.GetImages();
return Ok(numbers);
}
[HttpPost]
public IActionResult Post([FromBody] DataTransferObject transferObject)
{
return Ok(transferObject.PassedString + $" {transferObject.Zahl}");
}
}
}
TextRepository
TextFileParser.cs
using System;
using System.Collections.Generic;
using System.IO;
namespace TextRepository
{
public class TextFileParser
{
public static List<int> ReadAllData(string path)
{
if (!File.Exists(path))
{
throw new FileNotFoundException(path);
}
string[] allLines = System.IO.File.ReadAllLines(path);
if (allLines == null)
{
throw new Exception("War null");
}
if (allLines.Length < 1)
{
throw new Exception("Die Datei enthält 0 Zeilen.");
}
if (allLines.Length == 1)
{
return new List<int>();
}
List<int> numbers = new List<int>();
for (int i = 1; i < allLines.Length; i++)
{
if (int.TryParse(allLines[i], out int result))
{
numbers.Add(result);
}
else
{
Console.WriteLine($"In der Textdatei, in Zeile {i + 1}, ist eine inkorrekte Zahl aufgetreten.");
continue;
}
}
return numbers;
}
}
}
NumberTextFileRepository
using System.Collections.Generic;
namespace TextRepository
{
public class NumberTextFileRepository : INumberRepository
{
public List<int> GetNumbers()
{
System.IO.DirectoryInfo Root = new System.IO.DirectoryInfo(System.IO.Directory.GetCurrentDirectory());
return TextFileParser.ReadAllData(Root.Parent.FullName + "\\Textdatei.txt");
}
}
}
INumberRepository.cs
using System.Collections.Generic;
namespace TextRepository
{
public interface INumberRepository
{
List<int> GetNumbers();
}
}
ImageTransferRepository
ImageTransferRepository.cs
using System;
using System.Collections.Generic;
namespace ImageRepository
{
public class ImageTransferRepository : IImageTransferRepository
{
public List<System.Drawing.Bitmap> GetImages()
{
return ImageTransfer.GetImagesFromFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures));
}
}
}
ImageTransfer.cs
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace ImageRepository
{
public class ImageTransfer
{
public static List<System.Drawing.Bitmap> GetImagesFromFolder(string path)
{
if (!Directory.Exists(path))
{
throw new FileNotFoundException(path);
}
List<FileInfo> fileInfos = new List<FileInfo>();
fileInfos.AddRange(new DirectoryInfo(path).EnumerateFiles().Where(f => IsValidFile(f)));
fileInfos = fileInfos.OrderBy(x => x.CreationTime).ToList(); // The newest image should be at the top of the list.
return (from FileInfo file in fileInfos select new System.Drawing.Bitmap(file.FullName)).ToList();
}
private static bool IsValidFile(FileInfo File)
{
return File.FullName.ToLower().EndsWith(".bmp") ^ File.FullName.ToLower().EndsWith(".jpeg") ^ File.FullName.ToLower().EndsWith(".jpg") ^ File.FullName.ToLower().EndsWith(".png");
}
}
}
IImageTransferRepository.cs
using System.Collections.Generic;
using System.Drawing;
namespace ImageRepository
{
public interface IImageTransferRepository
{
List<Bitmap> GetImages();
}
}
I still want to use the call https://localhost:44355/api/My for displaying the numbers in the browser, i.e., the Get method. How can I make it so that I use a different link to transfer the image data? I am concerned with the call to the API – do I have to write a second Get function? – And about transferring the bytes of the images.
If you want to return List<int> numbers and List<System.Drawing.Bitmap> foundImages together,you can try to create a class which contains the two lists.Or you need to write a second Get function.
public class TestModel {
public List<int> numbers { get; set; }
public List<System.Drawing.Bitmap> foundImages { get; set; }
}
Get function:
[HttpGet]
public ActionResult<TestModel > Get()
{
TestModel t=new TestModel();
t.numbers = this.numberRepository.GetNumbers();
t.foundImages = this.imageRepository.GetImages();
return Ok(t);
}
If you want to create a second Get function,here is the demo:
[HttpGet]
public ActionResult<List<int>> Get()
{
List<int> numbers = this.numberRepository.GetNumbers();
return Ok(numbers);
}
[HttpGet("foundImages")]//route will be https://localhost:44355/api/My/foundImages
public ActionResult<List<System.Drawing.Bitmap>> Get()
{
List<System.Drawing.Bitmap> foundImages = this.imageRepository.GetImages();
return Ok(foundImages);
}

Alias a model's property to a different name in asp.net-core

I wish to alias the name of my request object properties, so that these requests both work and both go to the same controller:
myapi/cars?colors=red&colors=blue&colors=green and myapi/cars?c=red&c=blue&c=green
for request object:
public class CarRequest {
Colors string[] { get; set; }
}
Has anyone been able to use the new ModelBinders to solve this without having to write ModelBindings from scratch?
Here is a similar problem for an older version of asp.net and also here
I wrote a model binder to do this:
EDIT:
Here's the repo on github. There are two nuget packages you can add to your code that solve this problem. Details in the readme
It basically takes the place of the ComplexTypeModelBinder (I'm too cowardly to replace it, but I slot it in front with identical criteria), except that it tries to use my new attribute to expand the fields it's looking for.
Binder:
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MYDOMAIN.Client;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.Extensions.Logging;
namespace MYDOMAIN.Web.AliasModelBinder
{
public class AliasModelBinder : ComplexTypeModelBinder
{
public AliasModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory,
bool allowValidatingTopLevelNodes)
: base(propertyBinders, loggerFactory, allowValidatingTopLevelNodes)
{
}
protected override Task BindProperty(ModelBindingContext bindingContext)
{
var containerType = bindingContext.ModelMetadata.ContainerType;
if (containerType != null)
{
var propertyType = containerType.GetProperty(bindingContext.ModelMetadata.PropertyName);
var attributes = propertyType.GetCustomAttributes(true);
var aliasAttributes = attributes.OfType<BindingAliasAttribute>().ToArray();
if (aliasAttributes.Any())
{
bindingContext.ValueProvider = new AliasValueProvider(bindingContext.ValueProvider,
bindingContext.ModelName, aliasAttributes.Select(attr => attr.Alias));
}
}
return base.BindProperty(bindingContext);
}
}
}
Provider:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.Extensions.Logging;
namespace MYDOMAIN.Web.AliasModelBinder
{
public class AliasModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
foreach (var property in context.Metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
return new AliasModelBinder(propertyBinders,
(ILoggerFactory) context.Services.GetService(typeof(ILoggerFactory)), true);
}
return null;
}
/// <summary>
/// Setup the AliasModelBinderProvider Mvc project to use BindingAlias attribute, to allow for aliasing property names in query strings
/// </summary>
public static void Configure(MvcOptions options)
{
// Place in front of ComplexTypeModelBinderProvider to replace this binder type in practice
for (int i = 0; i < options.ModelBinderProviders.Count; i++)
{
if (options.ModelBinderProviders[i] is ComplexTypeModelBinderProvider)
{
options.ModelBinderProviders.Insert(i, new AliasModelBinderProvider());
return;
}
}
options.ModelBinderProviders.Add(new AliasModelBinderProvider());
}
}
}
Value Provider:
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Primitives;
namespace MYDOMAIN.Web.AliasModelBinder
{
public class AliasValueProvider : IValueProvider
{
private readonly IValueProvider _provider;
private readonly string _originalName;
private readonly string[] _allNamesToBind;
public AliasValueProvider(IValueProvider provider, string originalName, IEnumerable<string> aliases)
{
_provider = provider;
_originalName = originalName;
_allNamesToBind = new[] {_originalName}.Concat(aliases).ToArray();
}
public bool ContainsPrefix(string prefix)
{
if (prefix == _originalName)
{
return _allNamesToBind.Any(_provider.ContainsPrefix);
}
return _provider.ContainsPrefix(prefix);
}
public ValueProviderResult GetValue(string key)
{
if (key == _originalName)
{
var results = _allNamesToBind.Select(alias => _provider.GetValue(alias)).ToArray();
StringValues values = results.Aggregate(values, (current, r) => StringValues.Concat(current, r.Values));
return new ValueProviderResult(values, results.First().Culture);
}
return _provider.GetValue(key);
}
}
}
And an attribute to go in / be referenced by the client project
using System;
namespace MYDOMAIN.Client
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class BindingAliasAttribute : Attribute
{
public string Alias { get; }
public BindingAliasAttribute(string alias)
{
Alias = alias;
}
}
}
Configured in the Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services
...
.AddMvcOptions(options =>
{
AliasModelBinderProvider.Configure(options);
...
})
...
Usage:
public class SomeRequest
{
[BindingAlias("f")]
public long[] SomeVeryLongNameForSomeKindOfFoo{ get; set; }
}
leading to a request that looks either like this:
api/controller/action?SomeVeryLongNameForSomeKindOfFoo=1&SomeVeryLongNameForSomeKindOfFoo=2
or
api/controller/action?f=1&f=2
I put most things in my web project, and the attribute in my client project.

Return a single result in WCF

I am new to WCF and trying to make a simple CRUD service using Visual Studio 2012 and involving MSSQL stored procedures.
There is something I do not like about Load method. It is planned to return single entity by ID, but I have to use the .First() method because autogenerated Goods.Context.cs defines a result of GoodsLoad as ObjectResult<GoodsLoad_Result> instead of simple GoodsLoad_Result. How to make it autogenerated properly?
Goods.Context.cs (auto generated)
namespace StorehouseServer
{
public partial class storehouseEntities : DbContext
{
//.............................................
public virtual ObjectResult<GoodsLoad_Result> GoodsLoad(Nullable<int> iD)
{
var iDParameter = iD.HasValue ?
new ObjectParameter("ID", iD) :
new ObjectParameter("ID", typeof(int));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<GoodsLoad_Result>("GoodsLoad", iDParameter);
}
//.............................................
}
}
IGoods.cs
namespace StorehouseServer
{
[ServiceContract]
public interface IGoods
{
[OperationContract]
List<GoodsList_Result> List();
[OperationContract]
GoodsLoad_Result Load(int id);
}
}
Goods.svc.cs
namespace StorehouseServer
{
public class Goods : IGoods
{
public List<GoodsList_Result> List()
{
using (var db = new storehouseEntities())
{
return db.GoodsList().ToList();
}
}
public GoodsLoad_Result Load(int id)
{
using (var db = new storehouseEntities())
{
return db.GoodsLoad(id).First();
}
}
}
}
ID is an unique identifier in database and a simple select:
SELECT * FROM Goods WHERE ID=#ID;
is assumed to return a single row.
One of the fun things about EntityFramework is that you can use LINQ.
So you can do this:
db.GoodsLoad().Single(goods => goods.Id = id);
Or better yet, this:
db.GoodsLoad().Find(id);

How to override the Get method in Web API in C#.net

I have created web api using Entity Frame work in C#.net. I have a controller class in which i have defined some methods like this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Test_Net_Test_Info.Models;
namespace Test_Net_Test_Info.Controllers
{
public class InfosController : ApiController
{
public List<Info> Get()
{
return InfoRepository.GetAllInfos();
}
public Info Get(int id)
{
return InfoyRepository.GetInfoById(id);
}
public Info Get(string company)
{
return InfoRepository.GetInfoByCompany(company);
}
public Info Get(string contact)
{
return InfoRepository.GetInfoByContact(contact);
}
my InfoRepository class looks like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Test_Net_Test_Info.Models
{
public class InfoRepository
{
public static List<Info> GetAllInfoss()
{
Test_NETEntities dataContext = new Test_NETEntities();
var query = from info in dataContext.Infoss select info;
return query.ToList();
}
public static Info GetInfoById(int id)
{
test_NETEntities dataContext = new test_NETEntities();
v*emphasized text*ar query = (from info in dataContext.Infos where info.ID ==id select info).SingleOrDefault();
return query;
}
public static Info GetInfoByContact(string contact)
{
Test_NETEntities dataContext = new Test_NETEntities();
// var query = (from info in dataContext.Infos where info.Contact == contact select info).SingleOrDefault();
// return query;
//}
public static Info GetInfoByCompany(string company)
{
Test_NETEntities dataContext = new test_NETEntities();
var query = (from info in dataContext.Infos where info.Company == company select info).SingleOrDefault();
return query;
}
I am getting an error Test_Net_Test_Info.Controllers. already defines a member called 'Get' with the same parameter types. I am trying to retrieve data by Company name and Contact name. Kindly help me.
Your method signatures are ambiguous. Changing the name will fix the issue:
public Info GetByCompany(string company)
{
return InfoRepository.GetInfoByCompany(company);
}
public Info GetByContact(string contact)
{
return InfoRepository.GetInfoByContact(contact);
}
In both your method calls, the only thing the compiler sees is this (the signature):
Info GetByContact(string)
It doesn't know how to choose between the company and contact parameter.
This is one area where HTTP verbs and C# method signatures don't always get along. One approach could be to combine them into a single method:
public Info Get(string company = null, string contact = null)
{
if (company != null)
return InfoRepository.GetInfoByCompany(company);
if (contact != null)
return InfoRepository.GetInfoByContact(contact);
// throw an exception? some other default action?
}
Then you'd just be relying on the model binder to populate the method arguments based on the request.
(You might also use checks on string.IsNullOrWhiteSpace() instead of null.)

Strongly typed metadata in MEF2 (System.Composition)

I'm using the System.Composition namespace from the MEF for web and Windows Store apps NuGet package in a new ASP.NET MVC4 project.
I've read that in MEF2 you no longer use Lazy<IExtension, IExtensionMetadata>, but now you must provide a concrete type for the metadata view (and possibly use ExportFactory<> instead of Lazy<> ?).
However, I can't find any examples of how this should all work - just a few mentions of using a concrete type instead of an interface.
I've tried a few things, but keep getting the following error - "Export metadata for 'AccountID' is missing and no default value was supplied".
My code...
Creating the container (in Global.asax or App_Start folder):
// Get assemblies that will be providing imports and exports
var assemblies = GetAssemblies();
// Get conventions that will be used to find imports and exports
var conventions = GetConventions();
var container = new ContainerConfiguration().WithAssemblies(assemblies, conventions).CreateContainer();
// Create and apply a MefControllerFactory so controllers can be composed
ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container));
GetConventions() method:
private static ConventionBuilder GetConventions()
{
var conventionBuilder = new ConventionBuilder();
conventionBuilder.ForTypesDerivedFrom<IController>().Export();
conventionBuilder.ForTypesDerivedFrom<IExtension>().Export<IExtension>();
conventionBuilder.ForTypesMatching(t => t.Namespace != null && t.Namespace.EndsWith(".Parts")).Export().ExportInterfaces();
return conventionBuilder;
}
IExtension.cs:
public interface IExtension
{
void DoWork();
}
ExtensionMetadata.cs:
public class ExtensionMetadata
{
public int AccountID { get; set; }
}
ExtensionA.cs (same as ExtensionB.cs):
public void DoWork()
{
System.Diagnostics.Debug.WriteLine("ExtensionA doing work..");
}
ExtensionManager.cs:
public class ExtensionManager
{
private IEnumerable<ExportFactory<IExtension, ExtensionMetadata>> _extensions;
public ExtensionManager(IEnumerable<ExportFactory<IExtension, ExtensionMetadata>> extensions)
{
_extensions = extensions;
}
public void DoWork(int accountID)
{
foreach (var extension in _extensions)
{
if (extension.Metadata.AccountID == accountID)
{
extension.DoWork();
}
}
}
}
I think I'm missing something quite major here. Basically I want to lazily import all Extensions, check their metadata and if a condition is fulfilled have that extension do something.
Would really appreciate your feedback or any links to sample code / tutorials that cover my scenario.
Many thanks!
I think I've worked it out after reading this SO question.
I created a Metadata Attribute:
[MetadataAttribute]
public class ExtensionMetadataAttribute : ExportAttribute, IExtensionMetadata
{
public int AccountID { get; set; }
public ExtensionMetadataAttribute(int accountID) : base(typeof (IExtension))
{
AccountID = accountID;
}
}
Then modified ExtensionA.cs:
[ExtensionMetadata(1)]
public class ExtensionA : IExtension
{
public void DoWork()
{
System.Diagnostics.Debug.WriteLine("ExtensionA doing work..");
}
}
And now ExtensionManager.cs looks like this:
public class ExtensionManager : IExtensionManager
{
private readonly IEnumerable<ExportFactory<IExtension, ExtensionMetadata>> _extensions;
public ExtensionManager(IEnumerable<ExportFactory<IExtension, ExtensionMetadata>> extensions)
{
_extensions = extensions;
}
public void DoWork(int accountID)
{
foreach (var extension in _extensions)
{
if (extension.Metadata.AccountID == accountID)
{
using (var foo = extension.CreateExport())
{
foo.Value.DoWork();
}
}
}
}
}
This seems to do the trick, but I would still be interested in any feedback re best practices, performance issues etc.
Thanks!

Categories

Resources