Read file from model asp.net core 1.1 - c#

I need to read a json file from a class in Asp.net Core 1.1. The file is in the "wwwroot" of my mvc project.
I know I can take a dependency injection of IHostingEnvironment in controller, pass it down to my class, and access to WebRootPath, but I want to be able to instantiate my class outside a controller, e.g., inside another class.
How can I read a file which is in wwwroot folder inside my class?
Destination folder example:
wwwroot/keywords/keywords.json.
If I try to inject IHostingEnvironment in constructor of my class, I will need to pass a HostingEnvironment instance when I instantiate my class.
public class AppTweet
{
private readonly IHostingEnvironment _hostingEnvironment;
public AppTweet(ITweet tweet, ICollection<string> keyWords, IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
}
I call my class in a controller that passes down the hostingEnvironment, but I want to decouple this behaviour, because I need to instantiate this class outside a controller. E.g.:
public class Article()
{
public Article()
{
var appTweet = new AppTweet(new Tweet(), new List<string>, new HostingEnvironment());
}
}
If I do this way, HostingEnvironment.WebRootPath is null
EDIT
Maybe I don't need Dependency Injection. I just want to read a file inside the wwwroot folder, but I don't know how to access this path both in development and in server
When I try
`var file = System.IO.File.ReadAllText(#"..\wwwroot\keywords\keywords.json")`;
the file var does not read the project folder in my development machine. The path I want to read form is something like E:\SOLUTION_FOLDER\PROJECT_FOLDER\wwwroot\keywords, but it reads from E:\SOLUTION_FOLDER\wwwroot\keywords, and does not include the project folder.
EDIT 2
I think DI is not suitable in this case.
This is a personal project of my own. What I'm trying to do is populate some properties in this class. E.g.: summary of my class:
public class AppTweet
{
public AppTweet (ITweet tweet, ICollection<string> keyWords)
{
//Read a json file from wwwroot and populate NotDesiredKeyWords property
}
[NotMapped]
public IEnumerable<string> NotDesiredKeyWords { get; set; }
}
This class AppTweet must have access to this json file, that is like a configuration file, because it will always have to read these values. I know I can hard-code the values in the class (it will be faster), but I prefer to do it in a json file, so I don't have to compile the project if I add some value.
From what I could read here , Dependency Injection takes care of instantiate your class, and you can't have params in your constructor with no default values.
I need (not need but it's the more reasonable way to me) instantiate this class myself. And maybe instantiate this class inside another class, outside the controller. So, the only thing I want is to have access to the web root folder wwwroot (or some other folder) where I can put this json file, and it must work both in my machine and in the server. Which path I can pass to System.IO.File.ReadAllText(path) in order to read this file?

ASP.NET Core provides File Provider for such cases. In your scenario you need the instance of PhysicalFileProvider class (it is used to access the actual or physical file of the system). It could be created directly as
IFileProvider provider = new PhysicalFileProvider("root path");
or as your need to access files in wwwroot, the IHostingEnvironment.WebRootFileProvider extension property may be used:
//
// Summary:
// /// Gets or sets an Microsoft.Extensions.FileProviders.IFileProvider pointing
// at Microsoft.AspNetCore.Hosting.IHostingEnvironment.WebRootPath. ///
IFileProvider WebRootFileProvider { get; set; }
The possible way how to read your file:
// using System.IO;
// using Microsoft.AspNetCore.Hosting;
// IHostingEnvironment env
...
var fileProvider = env.WebRootFileProvider;
// here you define only subpath
var fileInfo = fileProvider.GetFileInfo("keywords\keywords.json");
var fileStream = fileInfo.CreateReadStream();
using (var reader = new StreamReader(fileStream))
{
string data = reader.ReadToEnd();
}
Also, note that you need to add Microsoft.Extensions.FileProviders.Physical package as a dependency.
If you want to create instances manually, then you need to pass dependency in cascading way... (and the instance of YourService should be created in the same manner or passed into another class as the dependency via constructor using DI...)
public class YourService
{
private IHostingEnvironment _env;
public YourService(IHostingEnvironment env)
{
_env = env;
}
public Article CreateArticle()
{
return new Article(_env);
}
}
public class AppTweet
{
public AppTweet(ITweet tweet, ICollection<string> keyWords, IHostingEnvironment hostingEnvironment)
{
// read file here for example
}
}
public class Article()
{
public Article(IHostingEnvironment hostingEnvironment)
{
var appTweet = new AppTweet(new Tweet(), new List<string>, hostingEnvironment);
}
}

Related

How to declare global variable in Program cs and use it in controllers in .NET 6.0 Web Api

I have default Program.cs file from Web Api template in .NET 6.0.
I am adding variable "test" so I can use its value in controllers.
var builder = WebApplication.CreateBuilder(args);
const string test = "test123";
builder.Configuration.Bind(test);
//rest of the file...
And now I want to use variable "test" outside Program.cs but I have no idea how to do it. I cannot just simply use it because when trying to read it in controller like this:
string localVar = test;
I am getting an error "'test' is not null here. Cannot use local variable or local function declared in a top-level statement in this context".
This is probably some stupid mistake but I can't figure it out...
Starting C# 9, we don't need to explicitly mention the Main method in Program.cs file as we can use the top-level statements feature. However, it doesn't mean that we shouldn't use the default Program class in the created file at all. In your case, you have a need to define the static/const property so you can change the newly created structure into the old one.
namespace WebApplication;
public class Program
{
public static string Test { get; private set; }
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
Program.Test = "approach1";
builder.Services.Configure<MyOptions>(x => x.Test = "approach2");
///
}
public class MyOptions
{
public string Test { get; set; }
}
I assumed that you have a need to set the value to the Program.Test field during runtime, so in the first approach, I used the static field with a private set; accessor instead of the constant.
In the second approach, I used the C# options feature to configure the MyOptions.Test field value, this will be very flexible and useful to write unit tests later. But, you need to inject the MyOptions class wherever is required.
In the below controller template, I specified how to access the configured values at Program.cs file, inside the Get method
public class TestController : ControllerBase
{
private readonly MyOptions _myOptions;
public TestController (IOptions<MyOptions> myOptions)
{
_myOptions = myOptions.Value;
}
public IActionResult Get()
{
string test1 = Program.Test;
string test2 = _myOptions.Test;
///
}
}
Add public partial class Program { } at the very end of your Program.cs file and add constant, property or whatever you like in there.

Access App key data from class libraries in .NET Core / ASP.NET Core

To access App Keys in a class library, do we need to do the following code in every class library and class where we need to access a AppKey?
public static IConfigurationRoot Configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
This is what I found in Microsoft docs, but this looks very redundant.
Startup class in a project as below
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup()
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework().AddEntityFrameworkSqlServer()
.AddDbContext<DbContext>(options =>
options.UseSqlServer(Configuration["Data:MyDb:ConnectionString"]));
}
}
Then how should I inject this "IConfigurationRoot" in each class of a project. And do I have to repeat this Startup class in each class Library? Why is this not part of .NET Core Framework?
The recommended way is to use the options pattern, provided by Microsoft and used heavily in ASP.NET Core.
Basically you create a strong typed class and configure it in the Startup.cs class.
public class MySettings
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
and initialize it in the Startup class.
// load it directly from the appsettings.json "mysettings" section
services.Configure<MySettings>(Configuration.GetSection("mysettings"));
// do it manually
services.Configure<MySettings>(new MySettings
{
Value1 = "Some Value",
Value2 = Configuration["somevalue:from:appsettings"]
});
then inject these options everywhere you need it.
public class MyService : IMyService
{
private readonly MySettings settings;
public MyService(IOptions<MySettings> mysettings)
{
this.settings = mySettings.Value;
}
}
By the principle of Information Hiding in Object-Oriented Programming, most classes should not need to have access to your application configuration. Only your main application class should need to directly have access to this information. Your class libraries should expose properties and methods to alter their behavior based on whatever criteria their callers deem necessary, and your application should use its configuration to set the right properties.
For example, a DateBox shouldn't need to know how timezone information is stored in your application configuration file - all it needs to know is that it has a DateBox.TimeZone property that it can check at runtime to see what timezone it is in.

Inject Dependency in View Location Expander

I have implemented a Custom ViewLocationExpander in a vnext project. I want to read a app setting value from the appsettings.json file in the ViewLocationExpander and hence the IOptions<> has been injected into the custom ViewLocationExpander 's constructor. However, while adding the custom ViewLocationExpander to the RazorViewEngine options an object of the ViewLocationExpander is required, which cannot be created due to the dependency.
Below is the code
public MyViewLocationExpander(IOptions<MyAppSettings> MyAppSettings)
{
var appSettings = MyAppSettings.Value;
client = appSettings.Client // client is a private field and is used in ExpandViewLocations function
}
MyAppSettings.cs is as below:
public class MyAppSettings
{
public string Client { get; set; }
}
In Startup.cs ConfigureServices method
services.Configure<RazorViewEngineOptions>(config =>
{
//config.ViewLocationExpanders.Add(new MyViewLocationExpander());
// MyViewLocationExpander cannot be created as it has a dependency on IOptions<MyAppSettings>
});
Any help on how to add the custom ViewLocationExpander to the RazorViewEngineOptions would be great.
One way to resolve services from the container is to resolve them in ExpandViewsMethod
using Microsoft.Extensions.DependencyInjection;
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
var service = context.ActionContext.HttpContext.RequestServices.GetService<IService>();
}

Access to Configuration object from Startup class

I would like to access to the Active Directory from my company in many controllers from my ASP.NET vNext project, and I inserted the domain name into my config.json file, so I can access it from the Configuration class. I find it heavy to always instantiate a new Configuration object at every time I want to access to my config.json, is there any way through the IConfiguration API to access to the Configuration class initialized into the Startup class ?
An example of how you can do this:
Let's assume you have a config.json like below:
{
"SomeSetting1": "some value here",
"SomeSetting2": "some value here",
"SomeSetting3": "some value here",
"ActiveDirectory": {
"DomainName": "DOMAIN-NAME-HERE"
}
}
Create a POCO type having your option information:
public class ActiveDirectoryOptions
{
public string DomainName { get; set; }
}
In Startup.cs, when configuring services:
services.Configure<ActiveDirectoryOptions>(optionsSetup =>
{
//get from config.json file
optionsSetup.DomainName = configuration.Get("ActiveDirectory:DomainName");
});
In all controllers which want to get this config setting, do something like...Here the options is injected by the DI system:
public class HomeController : Controller
{
private readonly IOptions<ActiveDirectoryOptions> _activeDirectoryOptions;
public HomeController(IOptions<ActiveDirectoryOptions> activeDirectoryOptions)
{
_activeDirectoryOptions = activeDirectoryOptions;
}
public IActionResult Index()
{
string domainName = _activeDirectoryOptions.Options.DomainName;
........
}
}
Responding to the comment:
Dependency Injection was one of my option, but assume that you inject many repository inside your controller and a UserManager object because you want some user management, your constructor will be very busy. And all the time you want to use your controller, an IOptions object will be instanciate, but what if you just want to use this object in one method of your controller ?
There are couple of options that I can think of:
From within the action, you can do
var options = HttpContext.RequestServices.GetRequiredService<IOptions<ActiveDirectoryOptions>>().Options;
You can have a parameter to the action which is decorated with FromServicesAttribute. This attribute will cause the parameter value to be retrieved from the DI.
Example:
public IActionResult Index([FromServices] IOptions<ActiveDirectoryOptions> options)
I prefer #2 over #1 as in case of unit testing it gives you information on all dependent pieces.

How can a component provide other components?

I want to have a component register other components in the registry as / after it's constructed. Let's say I have the following components:
interface IConfiguration
{
string SourceDirectory { get; }
string TargetDirectory { get; }
// other primitive-typed configuration parameters
}
class FileConfiguration : IConfiguration
{
// read parameters from some config file
}
class SourceDirectoryWrapper
{
public byte[] ReadFile(string filename)
{
// read a file from the source directory
}
public string Directory { get; set; }
}
class TargetDirectoryWrapper
{
public byte[] WriteFile(string filename)
{
// write a file into the source directory
}
public string Directory { get; set; }
}
class DirectoryWrapperFactory
{
public DirectoryWrapperFactory(IConfiguration config)
{
var source = new SourceDirectoryWrapper {
Directory = config.SourceDirectory
};
var target = new TargetDirectoryWrapper {
Directory = config.SourceDirectory
};
}
}
The components FileConfiguration and DirectoryWrapperFactory can be registered as is usual.
However, what I'd like to accomplish is to somehow "outject" the source and target objects created in DirectoryWrapperFactory. The basic idea is that different environments might require different configuration providers. (And even if not, I think it's a good idea to put reading configuration parameters into a separate component.)
I'd also like to have SourceDirectoryWrapper and TargetDirectoryWrapper managed in the IoC container. In my case, mainly for convenience – I have an EventSource implementation that I need everywhere, so I inject it using property autowiring. Every object not in the IoC container needs to have it passed explicitly, which kind of bugs me.
So: is this possible with AutoFac? If so, how? I poked at the lifecycle events but most don't allow access to the registry after an object is built.
I don't quite understand why DirectoryWrapperFactory needs to exist. You could just register SourceDirectoryWrapper and TargetDirectoryWrapper directly as part of normal wireup:
builder.Register(c => new SourceDirectoryWrapper {
Directory = c.Resolve<IConfiguration>().SourceDirectory
});
builder.Register(c => new TargetDirectoryWrapper {
Directory = c.Resolve<IConfiguration>().SourceDirectory
});

Categories