I'm having a bit of trouble with using Azure storage. I have an implementation at the moment which is fine but I want to expand it so that I am able to use multiple storage accounts/containers in one solution. I can't get my head around how to do that and still allow for dependency injection. I also need to be able to pass in settings which define the connection string and container name
This is how I'm doing it at the moment:
builder.Services.AddSingleton<IAzureStorageClient, AzureStorageClient>();
builder.Services.Configure<AzureStorageSettings>(configuration.GetSection("AzureStorageSettings"));
and then in the constructor
public AzureStorageClient(IOptions<AzureStorageSettings> options)
{
var azureStorageSettings = options.Value;
var cloudStorageAccount = GetCloudStorageAccount(azureStorageSettings.ConnectionString);
_blobClient = cloudStorageAccount.CreateCloudBlobClient();
_blobContainer = GetBlobContainer(azureStorageSettings.ContainerName);
}
I've read a lot of similar posts which mention using named registrations but I am using the built in IoC container and it doesn't allow for that. I've also seen posts saying to use a factory which looks good but I am hoping to package this logic and share it among different solutions and the factory solution would require a lot of configuration which I would like to avoid to make the package easy to consume.
Update:
I made the settings an interface to force it to be implemented each time it is required and I used the generic T to pass that into my storage client as follows:
public sealed class AzureStorageClient<T> : IAzureStorageClient<T> where T : class, IAzureStorageSettings, new()
public AzureStorageClient(IOptions<T> options)
{
var azureStorageSettings = options.Value;
var cloudStorageAccount = GetCloudStorageAccount(azureStorageSettings.ConnectionString);
_blobClient = cloudStorageAccount.CreateCloudBlobClient();
_blobContainer = GetBlobContainer(azureStorageSettings.ContainerName);
}
Then it could be injected like this:
builder.Services.AddSingleton<IAzureStorageClient<SpecificStorageSettings>, AzureStorageClient<SpecificStorageSettings>>();
Just an example, you can use settings like this:
Settings.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace UseDifferentSettings
{
public abstract class Settings
{
public abstract string connectingstring {
get;
}
public abstract string containername {
get;
}
public abstract string blobname {
get;
}
}
}
Setting1.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace UseDifferentSettings
{
class Setting1 : Settings
{
string _connectingstring = "DefaultEndpointsProtocol=https;AccountName=xxx;EndpointSuffix=core.windows.net";
string _containername = "video1";
string _blobname = "test.txt";
public override string connectingstring
{
get { return _connectingstring; }
}
public override string containername
{
get { return _containername; }
}
public override string blobname
{
get { return _blobname; }
}
}
}
Setting2.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace UseDifferentSettings
{
class Setting2 : Settings
{
private string _connectingstring = "DefaultEndpointsProtocol=https;AccountName=xxx;EndpointSuffix=core.windows.net";
private string _containername = "test";
private string _blobname = "test.txt";
public override string connectingstring
{
get { return _connectingstring; }
}
public override string containername
{
get { return _containername; }
}
public override string blobname
{
get { return _blobname; }
}
}
}
UploadToStorage.cs
using Azure.Storage.Blobs;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace UseDifferentSettings
{
public class UploadToStorage
{
Settings setting;
public UploadToStorage(Settings setting) {
this.setting = setting;
}
public void GoUpload() {
string connectingstring = setting.connectingstring;
string containername = setting.containername;
string blobname = setting.blobname;
string filecontent = "This is my test file content";
byte[] array = Encoding.ASCII.GetBytes(filecontent);
MemoryStream filestream = new MemoryStream(array);
BlobServiceClient blobServiceClient = new BlobServiceClient(connectingstring);
BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containername);
BlobClient blobClient = containerClient.GetBlobClient(blobname);
blobClient.Upload(filestream);
}
}
}
Program.cs(The main method class)
using System;
namespace UseDifferentSettings
{
class Program
{
static void Main(string[] args)
{
Settings setting1 = new Setting1();
Settings setting2 = new Setting2();
UploadToStorage uploadtostorage = new UploadToStorage(setting1);
uploadtostorage.GoUpload();
Console.WriteLine("Hello World!");
}
}
}
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.
My goal is to have a dictionary which stores different Container objects that derive from a interface IContainer. The user can
add different Container objects (as long as they implement IContainer) to this dictionary. The containers can add elements related to the
container (eg configContainer will add configElements, diContainer will add diConfigElements).
The elements also implement from a interface.
I want to avoid the scenario of DiConfigElements being added to ConfigContainer. I have looked at related questions and they dont quite solve my problem.
I feel that generics will solve my problem, I have a example but I get Argument 2: cannot convert from 'ConfigContainer' to 'IContainer'
I am using Unity C#.
test.cs
using System.Collections;
using System.Collections.Generic;
public class test
{
public Dictionary<string, IContainer<IResolveableItem>> containers;
// Use this for initialization
void Start()
{
containers = new Dictionary<string, IContainer<IResolveableItem>>();
ConfigContainer configContainer = new ConfigContainer();
ConfigContainerElement configElement = new ConfigContainerElement();
configElement.Name = "configTest";
configElement.Path = "configTest/configTest";
configContainer.Add("test1", configElement);
containers.Add("config",configContainer);
}
}
IContainer.cs
using System.Collections;
public interface IContainer<T> where T : IResolveableItem
{
void Add(string key , T value);
}
ConfigContainer.cs
using System.Collections.Generic;
public class ConfigContainer : IContainer<ConfigContainerElement>
{
public Dictionary<string, IResolveableItem> container = new Dictionary<string, IResolveableItem>();
public void Add(string key, ConfigContainerElement value)
{
throw new System.NotImplementedException();
}
}
ConfigContainerElement.cs
using System.Collections;
public class ConfigContainerElement : IResolveableItem
{
protected string name;
protected string path;
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public string Path
{
get
{
return path;
}
set
{
path = value;
}
}
}
IResolveableItem.cs
using System.Collections;
public interface IResolveableItem
{
string Name { get; set; }
string Path { get; set; }
}
It's a limitation of generics, will have to use runtime checking.
I am trying to figure out how to get Serilog to log the class name and method/line number, like I would see with log4cxx in C++.
I tried my best to grab all the relevant bits out of the real code I am working on and come up with a minimal example.
I've also been Googling Serilog left and right, but I am not finding good documentation. I suppose it is because there are so many libraries on top of the base serilog and each needs there own docs to tell me how to do things.
I can see the basics on configuration at https://github.com/serilog/serilog/wiki/Configuration-Basics , but this seems to use a TextWriter sink from a seperate Serilog library and a custom formatter, both of which I don't really understand.
I can also find examples on stack overflow that use the simple configuatation and the enrich call to log the class and method names.
C# ASP.NET Core Serilog add class name and method to log
I am not able to get it to log them. How can I get this to log the class and method name or line number while still using the custom formatter and TextWriter?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Serilog;
using Serilog.Events;
using Serilog.Formatting;
namespace SerilogDemo {
// Someone else made this, I just changed the name to protect the innocent
public class SomeonesLogTextFormatter : ITextFormatter
{
public void Format(LogEvent logEvent, TextWriter output)
{
output.Write(logEvent.Level);
output.Write(": ");
logEvent.MessageTemplate.Render(logEvent.Properties, output);
output.WriteLine();
if (logEvent.Exception != null)
{
output.WriteLine(logEvent.Exception);
}
}
}
public class SomeClass
{
private Serilog.ILogger _log = Serilog.Log.ForContext<SomeClass>();
public SomeClass()
{
_log.Debug("SomeClass has been instantiated");
}
public void Foo()
{
_log.Debug("Foo has been called");
}
}
class Program
{
static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.Enrich.FromLogContext()
.WriteTo.TextWriter(textWriter: Console.Out, formatter: new SomeonesLogTextFormatter())
.CreateLogger();
var poop = new SomeClass();
poop.Foo();
}
}
}
The Serilog way of adding more information to a log message is by adding properties to the LogContext either manually or by using an Enricher that does that for you. Read more about Serilog Enrichment.
Serilog by default does not capture that information, and that can be quite expensive if you do for every single message, but the way to do it is by using C#'s Caller Information feature such as CallerMemberName, CallerFilePath, CallerLineNumber.
Here is an example, also copied below:
public static class SerilogWithCallerAttributes
{
public static void Main()
{
Serilog.Log.Logger = new LoggerConfiguration()
.WriteTo.ColoredConsole()
.CreateLogger();
GoDoSomething();
}
public static void GoDoSomething()
{
int score = 12;
Log.Information("Player scored: {Score}", CallerInfo.Create(), score);
}
}
public static class Log
{
public static void Information(string messageTemplate, CallerInfo callerInfo, params object[] propertyValues)
{
Serilog.Log.Logger
.ForHere(callerInfo.CallerFilePath, callerInfo.CallerMemberName, callerInfo.CallerLineNumber)
.Information(messageTemplate, propertyValues);
}
}
public static class LoggerExtensions
{
public static ILogger ForHere(
this ILogger logger,
[CallerFilePath] string callerFilePath = null,
[CallerMemberName] string callerMemberName = null,
[CallerLineNumber] int callerLineNumber = 0)
{
return logger
.ForContext("SourceFile", callerFilePath)
.ForContext("SourceMember", callerMemberName)
.ForContext("SourceLine", callerLineNumber);
}
}
public class CallerInfo
{
public string CallerFilePath { get; private set; }
public string CallerMemberName { get; private set; }
public int CallerLineNumber { get; private set; }
private CallerInfo(string callerFilePath, string callerMemberName, int callerLineNumber)
{
this.CallerFilePath = callerFilePath;
this.CallerMemberName = callerMemberName;
this.CallerLineNumber = callerLineNumber;
}
public static CallerInfo Create(
[CallerFilePath] string callerFilePath = null,
[CallerMemberName] string callerMemberName = null,
[CallerLineNumber] int callerLineNumber = 0)
{
return new CallerInfo(callerFilePath, callerMemberName, callerLineNumber);
}
}
I'm working on a WCF service and trying to cleanup code. Issue I'm running into is I have a base data type I want to send as results from the service to the client; however in the service code itself it will mostly make use of classes derived on the base with additional properties for processing. The client has no reason to know about these at all.
Right now I can get this working but only if I define the derived classes in the shared library. I do not want them in there as they are specific solely to the service.
Below is example to show the problem. All three files are in separate projects in the same solution.
Common.IPersonService.cs
using System.ServiceModel;
using System.Runtime.Serialization;
namespace Common
{
[ServiceContract]
public interface IPersonService
{
[OperationContract(Name = "GetPersonById")]
Person GetPersonById(int id);
}
[DataContract(Name = "Person")]
public class Person
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
}
}
WcfClient.Program.cs
using System;
using System.ServiceModel;
using Common;
namespace WcfClient
{
class Program
{
static void Main(string[] args)
{
var binding = new NetTcpBinding();
var endpoint = new EndpointAddress("net.tcp://localhost:8001/WcfTest/");
var factory = new ChannelFactory<IPersonService>(binding, endpoint);
IPersonService service = null;
try
{
service = factory.CreateChannel();
Person result = service.GetPersonById(5);
Console.WriteLine(result.Name);
((ICommunicationObject)service).Close();
Console.ReadLine();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
}
}
}
}
WcfService.Program.cs
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Description;
using Common;
namespace WcfService
{
[DataContract(Name = "Person")]
public class Contact : Person
{
public string Address { get; set; }
}
class Program
{
static void Main(string[] args)
{
using (ServiceHost serviceHost = new ServiceHost(typeof(PersonService)))
{
serviceHost.Open();
Console.WriteLine("Service started");
Console.ReadLine();
}
}
}
public class PersonService : IPersonService
{
private Dictionary<int, Contact> _testData = new Dictionary<int, Contact>();
public PersonService()
{
Random rnd = new Random();
for (int i = 0; i < 100; i++)
{
_testData.Add(i + 1, new Contact()
{
Id = i + 1,
Name = Guid.NewGuid().ToString(),
Address = Guid.NewGuid().ToString()
});
}
}
public static void Configure(ServiceConfiguration config)
{
config.AddServiceEndpoint(typeof(IPersonService), new NetTcpBinding(), "net.tcp://localhost:8001/WcfTest/");
config.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true });
}
public Person GetPersonById(int id)
{
return _testData[id];
}
public Person GetValueByKey(string key)
{
return null;
}
}
}
The exception received is the following:
The socket connection was aborted. This could be caused by an error
processing your message or a receive timeout being exceeded by the
remote host, or an underlying network resource issue. Local socket
timeout was '00:00:59.9780000'.
Now if I move the Contact class from the WcfService project and put it in the Common project it will work. As said though I'd prefer not to muddy a common library with items specific to the service implementation.
Thanks!
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...