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);
}
Related
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.
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.
I have one class GitHub with one method which should return list of all commits for specific username and repo on GitHub:
using System;
using Octokit;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace ReadRepo
{
public class GitHub
{
public GitHub()
{
}
public async Task<List<GitHubCommit>> getAllCommits()
{
string username = "lukalopusina";
string repo = "flask-microservices-main";
var github = new GitHubClient(new ProductHeaderValue("MyAmazingApp"));
var repository = await github.Repository.Get(username, repo);
var commits = await github.Repository.Commit.GetAll(repository.Id);
List<GitHubCommit> commitList = new List<GitHubCommit>();
foreach(GitHubCommit commit in commits) {
commitList.Add(commit);
}
return commitList;
}
}
}
And I have main function which call getAllCommits method:
using System;
using Octokit;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace ReadRepo
{
class MainClass
{
public static void Main(string[] args)
{
GitHub github = new GitHub();
Task<List<GitHubCommit>> commits = github.getAllCommits();
commits.Wait(10000);
foreach(GitHubCommit commit in commits.Result) {
foreach (GitHubCommitFile file in commit.Files)
Console.WriteLine(file.Filename);
}
}
}
}
When I run this i get following error:
Problem is because this variable commit.Files is Null probably because of async call but I am not sure how to solve it. Please some help?
My guess is if you need to get a list of files for all the commits you will need to get each commit separately by using
foreach(GitHubCommit commit in commits)
{
var commitDetails = github.Repository.Commit.Get(commit.Sha);
var files = commitDetails.Files;
}
Take a look at this. There's also decribed another approach to reach your goal - at first get the list of all files in a repository and then get the list of commits for each file.
I am trying to use the method of binding located here but having no luck
https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface
https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface%3A-Referencing-Named-Bindings
Keep in mind I am not trying to do it this way:https://gist.github.com/akimboyko/4593576
Rather I am trying to use the convention GetMercedes() to mean
I am basically trying to achieve this:https://gist.github.com/akimboyko/4593576 with conventions specified in the above examples.
using Ninject;
using Ninject.Extensions.Factory;
using Ninject.Modules;
using Ninject.Parameters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Test.NinjectFactory
{
public class Program
{
public static void Main()
{
using (var kernel = new StandardKernel(new CarModule()))
{
var factory = kernel.Get<ICarFactory>();
var mercedes =factory.GetMercedes();
int i = 1;
}
}
public interface ICar
{
void Drive();
}
public class Mercedes : ICar
{
readonly ICarFactory carFactory;
public Mercedes(ICarFactory carFactory)
{
this.carFactory = carFactory;
}
public void Drive()
{
var Mercedes = this.carFactory.GetMercedes();
}
}
public interface ICarFactory
{
ICar GetMercedes();
}
public class CarModule : NinjectModule
{
public override void Load()
{
//https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface%3A-Referencing-Named-Bindings
Kernel.Bind<ICarFactory>().ToFactory();
Bind<ICar>().To<Mercedes>().NamedLikeFactoryMethod<ICarFactory>(x => x.GetMercedes());//doesnt work for me
}
}
}
}
I'm posting this as an answer because it is most likely the cause.
The factory extensions use prefixed Get methods as a standard. You'll run into issues by calling any of your factory methods with prefixed Get and using NamedLikeFactoryMethod. For example, GetFord, GetMercedes, GetNissan. You'll notice that, in the example at the link you provided, the function is called CreateMercedes.
Change your function name to CreateMercedes or anything that doesn't start with Get and it should be fine.
I found the anwswer here:
https://gist.github.com/akimboyko/5338320
It seems you need a function to take care of the binding
public class BaseTypeBindingGenerator<InterfaceType> : IBindingGenerator
{
public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
{
if (type != null && !type.IsAbstract && type.IsClass && typeof(InterfaceType).IsAssignableFrom(type))
{
string.Format("Binds '{0}' to '{1}' as '{2}", type, type.Name, typeof(InterfaceType)).Dump();
yield return bindingRoot.Bind(typeof(InterfaceType))
.To(type)
.Named(type.Name) as IBindingWhenInNamedWithOrOnSyntax<object>;
}
}
}
I'm trying to compile small fragments of C# into JavaScript using the Script# compiler.
But I don't get anything in return, GetStream() in my MemoryStreamSource is not even being called, so I must be doing something wrong.
Here's my code:
CodeScriptCompiler csc = new CodeScriptCompiler();
return csc.CompileCSharp("String.IsNullOrWhiteSpace(Model.MobilePhoneNumber)");
CodeScriptCompiler.cs
using System;
using System.Collections.Generic;
using ScriptSharp;
namespace CodeToScriptCompiler
{
public class CodeScriptCompiler
{
ScriptCompiler sc = new ScriptCompiler();
public string CompileCSharp(string csharpCode)
{
string errorMessages = String.Empty;
CompilerOptions options = new CompilerOptions();
options.Defines = new List<string>();
options.References = new List<string>();
options.References.Add("System.dll");
options.Resources = new List<IStreamSource>();
options.Sources = new List<IStreamSource>();
options.Sources.Add(new MemoryStreamSource(csharpCode));
options.TemplateFile = new MemoryStreamSource(csharpCode);
MemoryStreamDestination output = new MemoryStreamDestination();
options.ScriptFile = output;
if (!options.Validate(out errorMessages))
{
return errorMessages;
}
return output.GetCompiledCode();
}
}
}
MemoryStreamSource.cs
using System.IO;
using System.Text;
using ScriptSharp;
namespace CodeToScriptCompiler
{
public class MemoryStreamSource : IStreamSource
{
private string _code;
private MemoryStream _memoryStream;
public MemoryStreamSource(string code)
{
this._code = code;
}
public string Name
{
get { return "InMemoryCode"; }
}
public string FullName
{
get { return "InMemoryCode"; }
}
public void CloseStream(Stream stream)
{
stream.Close();
}
public Stream GetStream()
{
this._memoryStream = new MemoryStream(Encoding.ASCII.GetBytes(this._code));
return this._memoryStream;
}
}
}
MemoryStreamDestination.cs
using System;
using System.IO;
using ScriptSharp;
namespace CodeToScriptCompiler
{
public class MemoryStreamDestination : IStreamSource
{
private MemoryStream _memoryStream;
private string _compiledCode;
public string Name
{
get { return "MemoryStreamDestination"; }
}
public string FullName
{
get { return "MemoryStreamDestination"; }
}
public void CloseStream(Stream stream)
{
if (String.IsNullOrWhiteSpace(this._compiledCode))
{
this._compiledCode = this.GetCompiledCode();
}
stream.Close();
}
public Stream GetStream()
{
this._memoryStream = new MemoryStream();
return this._memoryStream;
}
public string GetCompiledCode()
{
if (!String.IsNullOrWhiteSpace(this._compiledCode))
{
return this._compiledCode;
}
if (this._memoryStream != null)
{
using (StreamReader sr = new StreamReader(this._memoryStream))
{
return sr.ReadToEnd();
}
}
return String.Empty;
}
}
}
Some things I see potentially problematic.
TemplateFile is set to a c# code stream. Leave it unset, since that is not a valid template.
References should include the script# mscorlib, and furthermore, only full paths to valid script# assemblies. System.dll is not a script# assembly.
Before you read from the MemoryStream, you need to set the stream position back to the start, otherwise it is at the end after the compiler has written to it, and there is nothing more to read.
Not seeing a call to Compile on the Compiler instance you created, passing in the options instance. My guess is you did do that, just not there in the stack overflow snippet.
You probably should also implement IErrorHandler and pass that to the compiler to get error messages should they occur, once you have the basic thing working.
For reference you can also look at the unit tests at https://github.com/nikhilk/scriptsharp/tree/master/tests/ScriptSharp/Core which does something similar.
Note that you'll need a valid c# source file, rather than a single standalone expression. You can however likely deal with that by stripping off stuff from the start and end of the resulting script to get the script for just the expression you care about.
Hope that helps.
I am certainly interested/curious to understand how you're using this, and where you're compiling c# to script dynamically...