When you create code in Visual Studio, it runs from a temp directory. But at a hosting provider (one that specifically hosts ASP.NET sites), it will be in a different directory (typically an app server virtual folder with IIS). I need to have uploaded files in a directory the IIS web server can find them, so the use case below will work.
Q: How can I define my links in MVC Controllers/Views so that they work both in development as well as in production (upload & download)?
Use Case: My app needs to allow a file to be uploaded (still don't know where on dev box to upload to), and then via a link on another page download that same file.
In case you're in need of a more detailed answer I'll give you some hints and hopefully this will get you on the right track.
Define a contract so you can abstract any concrete file provider implementation in order to minimize the impact and modifications to your code if you need to change the underlying file storage later. For instance if you go to Azure in a future version you can implement an AzureFileProvider and store your file using Blobs.
public interface IFileProvider
{
UploadResult UploadFile(byte[] fileContent, string fileName);
DownloadResult DownloadFile(int fileId);
}
Implement your interface for a file system-based file provider:
public class FileSystemFileProvider : IFileProvider
{
public FileSystemFileProvider(string absouteBasePath)
{
//Check if absoluteBasPath exists and create the directory if it doesn't.
if (!Directory.Exists(absouteBasePath))
Directory.CreateDirectory(absouteBasePath);
}
public UploadResult UploadFile(byte[] fileContent, string fileName)
{
//TODO: Your upload file implementation here
}
public DownloadResult DownloadFile(int fileId)
{
//TODO Your download file implementation here
}
}
You can adjust your contract according to your needs. Here in this sample UploadResult and DownloadResult are simple classes to retrieve the result of the operations. For example this could be a definition for DownloadResult:
public class DownloadResult
{
public bool Success { get; set; }
public byte[] FileContent { get; set; }
public string DownloadName { get; set; }
public string SizeInBytes { get; set; }
//... any other useful properties
}
In your StartupAuth.cs or Global.asax (the place where your bootstrapper code runs, initialize the file provider with your concrete implementation. This approach works better with an IoC container in place. You can use AutoFac, Ninject or any other you like, but please do your self a favor and use one.
//read the base path from web.config so you may adjust it on production hosting provider without need to make and compile the app
var fileBasePath = ConfigurationManager.AppSettings["fileBasePath"];
//get the path as an absolute file system path
var absoluteBasePath = Server.MapPath(fileBasePath);
//Initialize the file provider with the absolute path
var fileProvider = new FileSystemFileProvider(absoluteBasePath);
//TODO: Configure your IoC to return the fileProvider instance when an IFileProvider is requested.
As you see in step 2 I read an application setting value from the web.config file. This should be present on appSettings section in your configuration file.
<appSettings>
<add key="fileBasePath" value="~/FileStorage"/>
</appSettings>
With all this in place you could inject your fileProvider to your controllers like this:
public class UploadController : Controller
{
private readonly IFileProvider fileProvider;
public UploadController(IFileProvider fileProvider)
{
this.fileProvider = fileProvider;
}
And then you can use your provider on the relevant action when need it.
[HttpPost]
public ActionResult Upload(FileUploadModel model)
{
//Validate
if (ModelState.IsValid)
{
//Use your fileProvider here to upload the file
var content = new byte[model.PostedFile.ContentLength];
model.PostedFile.InputStream.Write(content, 0, content.Length);
var result = fileProvider.UploadFile(content, model.PostedFile.FileName);
if (result.Success)
{
//TODO: Notify the user about operation success
return RedirectToAction("Index", "Upload");
}
}
//
return View(model);
}
Hope this helps!
PS: I don't know why code formatting is a little bit crazy.
Related
I'm trying to use already configured custom config-class to configure another service. Configuration gets data from local settings and Azure AppConfiguration store. This is my Startup code:
public void ConfigureServices(IServiceCollection services)
{
services.AddAzureAppConfiguration();
services.Configure<CustomConfig>(Configuration);
services.AddTransient<ISomeService, SomeService>((serviceProvider) =>
{
CustomConfig config = serviceProvider.GetRequiredService<IOptions<CustomConfig>>().Value;
return new SomeService(config);
});
//--------------------------------------------------------------------
services.AddRazorPages();
services.AddControllers();
}
But when SomeService is instantiated, my custom config-object doesn't contain data that should come from the Azure AppConfig. It has only data from the appsettings.json.
What is wrong and what can I do here?
So the short answer is: it's actually working.
I was suspecting some stupid error, and it was exactly the case (a couple of code lines was commented so data was not retrieved from Azure - shame on me).
Thank you #pinkfloydx33 for the reassurance that the pattern should work.
And in case if someone wonders about binding root config values - it works as well.
In my case, appsettings.json contains root values that I need to connect to Azure AppConfig store (primary and secondary endpoints, refresh interval and environment name which is used in key labels) and a few sections corresponding with external services: database, AAD B2C etc which are retrieved from Azure AppConfig.
So my custom class has root values and a few nested classes like this:
public class CustomConfig : ConfigRoot
{
// These root values come from appsettings.json or environment variables in Azure
[IsRequired]
public string Env { get; set; }
[IsRequired, Regex("RegexAppConfigEndpoint")]
public Uri AppConfigEndpointPrimary { get; set; }
[IsRequired, Regex("RegexAppConfigEndpoint")]
public Uri AppConfigEndpointSecondary { get; set; }
public int AppConfigRefreshTimeoutMinutes { get; set; } = 30;
// And these sections come from the Azure AppConfig(s) from the above
public ConfigSectionDb Db { get; set; } = new ConfigSectionDb();
public ConfigSectionB2c B2c { get; set; } = new ConfigSectionB2c();
// Some other sections here...
}
Here ConfigSection<...> classes contain in their turn other subclasses. So I have quite a hierarchy here. ConfigRoot here is an abstract class providing Validate method.
And it works: this services.Configure<CustomConfig>(Configuration); part gets all the data - the root and the sections from all configured providers. In my case, it's two Azure AppConfigs, appsettings.json, environment variables.
I am creating an automated test framework to test an API using .NetCore, RestSharp and MsTest. I am looking for ideas to run my tests for 4 different environments (2 different countries, test and live environment each). I will access test data through appsettings.json file (e.g appsettings.eutest.json, appsetings.detest.json, etc).
I could use something like [DynamicData()] to pass each test an argument with the key to access each individual .json file, but there is a subset of tests, compatibility tests, where I need to run also again different versions of the API, this is needed to ensure backward compatibility.
[DynamicData("NameOfPropertyWithEnvironments", typeof(BaseClass))]
[TestMethod]
public void RegularTest(string envToTest)
{
//-- Logic to access data on the .json file
}
[DynamicData("NameOfPropertyWithVersions", typeof(BaseClass))]
[TestMethod]
public void CompatibilityTest(int versionBackToTest)
{
}
So far, I have been using also [DynamicData()] to pass the versions to be tested as an argument of those particular tests, but I am lost as to how to combine different environments and different versions.
Thanks in advance!
To run these tests locally, you can create a local.settings.json file in your test project with APIEndpoint and APIVersion settings.
Next, implement a configuration manager that can firstly determine whether you are running the tests locally. If so, read the settings from local.settings.json. If not, read from environment variables. Here is an example of what I use:
namespace SomeIntegrationTests.configuration
{
class Configuration
{
public string API_ENDPOINT { get; set; }
public string API_VERSION { get; set; }
}
class ConfigurationManager
{
private const string LocalSettingsFile = "local.settings.json";
private static Configuration _configuration;
public static Configuration Get()
{
if (_configuration == null)
{
if (IsLocalRun())
{
var localSettings = ReadFile(LocalSettingsFile);
_configuration = JsonConvert.DeserializeObject<Configuration>(localSettings);
}
else
{
_configuration = GetFromEnvironmentVariables();
Console.WriteLine($"Retrieved configuration from environment variables.");
}
}
return _configuration;
}
private static Configuration GetFromEnvironmentVariables()
{
var environment = GetEnvironmentVariable("CURRENT_ENVIRONMENT");
Console.WriteLine($"Current environment is {environment}");
return new Configuration
{
API_ENDPOINT = GetEnvironmentVariable($"API_ENDPOINT.{environment}"),
API_VERSION = GetEnvironmentVariable($"API_VERSION.{environment}")
};
}
private static string GetEnvironmentVariable(string key)
{
return Environment.GetEnvironmentVariable(key);
}
private static bool IsLocalRun()
{
var environment = GetEnvironmentVariable("CURRENT_ENVIRONMENT");
return environment == "local";
}
private string ReadFile(string file)
{
var projectFolderLocation = // depends on the type of implementation get location of where the local.settings.json is located;
var filePath = Path.Combine(projectFolderLocation, "configuration", file);
return File.ReadAllText(filePath);
}
}
}
On your local pc, add an environment variable for "CURRENT_ENVIRONMENT" with value "local" and add local.appsettings.json in your project. In your pipeline add a step to set environment variable values accordingly. When you run the tests during pipeline, settings will be used from the environment variables settings.
Then from your test, you can load the Configuration via ConfigurationManager implementation and then call API_ENDPOINT and API_VERSION.
class SomethingTests
{
private readonly string APIEndpoint;
private readonly string APIVersion;
public SomethingTests()
{
var configuration = ConfigurationManager.Get();
APIEndpoint = configuration.API_ENDPOINT;
APIVersion = configuration.API_VERSION;
}
[TestMethod]
public void SomethingTests()
{
// use APIEndpoint & APIVersion
}
}
As for running tests against production environments, this doesn't seem like a good idea. You don't want to insert test data on production. Implement automated deployment steps to ensure you tested your API code on test environments and deploy to production after that.
Does this help?
I need to create a nuget package which will contain shared views, controllers, js, and css files to be used across multiple other projects. Essentially a modular set of things like checkout or search pages that can be dropped into other site projects.
All the research I've done so far points to utilizing precompiled views with RazorGenerator but doesn't say much about the controllers, js, and css files.
Ideally a module's views and other files should be able to be overridden by the consuming host project but the files themselves should not be directly editable within the host project. Much like dlls referenced when other nuget packages are added.
The answers and posts about this type of subject I've found so far seem a bit dated.
Is there a cleaner, modern solution for creating an ASP.NET MVC module nuget package so that fully working pages are able to be shared across projects?
Controllers
Use area's and register those area's. Possibly this is not natively supported and you might need to overwrite some parts in mvc4. look at:
How do I register a controller that has been created in an AREA
http://netmvc.blogspot.be/2012/03/aspnet-mvc-4-webapi-support-areas-in.html
As long as the dll's are loaded in you can always register all classes that are subclasses of Controller with reflection (on application startup).
Razor
Precompiling is possible, but only really adviseable in dotnet core since it is a first class citizen there.
You could also add the views as content which is injected into the project.
Downside:
On update it overwrites the views (if you changed them you lost changes)
Pros:
On update you can merge both changes in git
Easily change the already existing razor pages
I found a solution that works for us. We have not yet fully implemented this so some unforeseen issues may still arise.
First, create an MVC project with the needed views, controllers, javascript, etc needed to match your package requirements. Each static file and view must be set as embedded resources in the project.
Then, add a class to serve these files on a virtual path provider. This will allow a consuming project to access the static files and views as if they were within the same project.
To enable custom routing an implementation of the RouteBase class will be required. This implementation needs to accept a string property for which virtual routes are based to allow the host to apply whichever route prefix is desired. For our example, the property will default to Booking with the associated architecture of views within our project to match.
Both the RouteBase implementation and the VirtualPath class will be instantiated within a setup method. This will allow a consuming project to call a single method to setup the booking engine. This method will take in the sites Route Collection and dynamic route property to append the custom routes. The method will also register the VirtualPathProvider to the HostingEnvironment object.
The consuming host can also override views and any other static file by simply having a file in a location within the host project that matches the path of the file or view in the booking engine.
Some Code Examples
RouteBase method which returns correct route values if an incoming route matches a virtual route.
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
// Trim the leading slash
var path = httpContext.Request.Path.Substring(1);
// Get the page that matches.
var page = GetPageList(httpContext)
.Where(x => x.VirtualPath.Equals(path))
.FirstOrDefault();
if (page != null)
{
result = new RouteData(this, new MvcRouteHandler());
// Optional - make query string values into route values.
AddQueryStringParametersToRouteData(result, httpContext);
result.Values["controller"] = page.Controller;
result.Values["action"] = page.Action;
}
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return result;
}
RouteBase Virtual Route to NuGet Package Route mapping. New PageInfo objects created with dynamic virtual path string and references to real controller and action names. These are then stored in the http context cache.
private IEnumerable<PageInfo> GetPageList(HttpContextBase httpContext)
{
string key = "__CustomPageList";
var pages = httpContext.Cache[key];
if (pages == null)
{
lock (synclock)
{
pages = httpContext.Cache[key];
if (pages == null)
{
pages = new List<PageInfo>()
{
new PageInfo()
{
VirtualPath = string.Format("{0}/Contact", BookingEngine.Route),
Controller = "Home",
Action = "Contact"
},
};
httpContext.Cache.Insert(
key: key,
value: pages,
dependencies: null,
absoluteExpiration: System.Web.Caching.Cache.NoAbsoluteExpiration,
slidingExpiration: TimeSpan.FromMinutes(1),
priority: System.Web.Caching.CacheItemPriority.NotRemovable,
onRemoveCallback: null);
}
}
}
return (IEnumerable<PageInfo>)pages;
}
Booking Engine class setup method which does all the instantiating needed for the assembly.
public class BookingEngine
{
public static string Route = "Booking";
public static void Setup(RouteCollection routes, string route)
{
Route = route;
HostingEnvironment.RegisterVirtualPathProvider(
new EmbeddedVirtualPathProvider());
routes.Add(
name: "CustomPage",
item: new CustomRouteController());
}
}
EmbeddedVirtualFile
public override CacheDependency GetCacheDependency(string virtualPath, virtualPathDependencies, DateTime utcStart)
{
string embedded = _GetEmbeddedPath(virtualPath);
// not embedded? fall back
if (string.IsNullOrEmpty(embedded))
return base.GetCacheDependency(virtualPath,
virtualPathDependencies, utcStart);
// there is no cache dependency for embedded resources
return null;
}
public override bool FileExists(string virtualPath)
{
string embedded = _GetEmbeddedPath(virtualPath);
// You can override the embed by placing a real file at the virtual path...
return base.FileExists(virtualPath) || !string.IsNullOrEmpty(embedded);
}
public override VirtualFile GetFile(string virtualPath)
{
// You can override the embed by placing a real file at the virtual path...
if (base.FileExists(virtualPath))
return base.GetFile(virtualPath);
string embedded = _GetEmbeddedPath(virtualPath);
if (string.IsNullOrEmpty(embedded))
return null;
return new EmbeddedVirtualFile(virtualPath, GetType().Assembly
.GetManifestResourceStream(embedded));
}
private string _GetEmbeddedPath(string path)
{
if (path.StartsWith("~/"))
path = path.Substring(1);
path = path.Replace(BookingEngine.Route, "/");
//path = path.ToLowerInvariant();
path = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name + path.Replace('/', '.');
// this makes sure the "virtual path" exists as an embedded resource
return GetType().Assembly.GetManifestResourceNames()
.Where(o => o == path).FirstOrDefault();
}
Nested Virtual File Class
public class EmbeddedVirtualFile : VirtualFile
{
private Stream _stream;
public EmbeddedVirtualFile(string virtualPath,
Stream stream) : base(virtualPath)
{
if (null == stream)
throw new ArgumentNullException("stream");
_stream = stream;
}
public override Stream Open()
{
return _stream;
}
}
A lot of the code we are using comes from the following links
Embedded Files - https://www.ianmariano.com/2013/06/11/embedded-razor-views-in-mvc-4/
RouteBase implementation - Multiple levels in MVC custom routing
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);
}
}
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
});