Hi @Harry Ridpath ,
Welcome to Microsoft Q&A forum.
Can you tell us your application version, whether it is an Asp.net 5 application?
According to the link, I created an Asp.net 5 MVC application (named CoreMVC5Sample) and tested the related code, everything works well on my side. You can refer to it (please note the package/namespace reference, the issue might relate them): In this sample, I'm using the IWebHostEnvironment
and Newtonsoft
(in this sample, I use the Newtonsoft.Json 13.0.1 package, you can install it via Nuget).
[Note] The following code also works well in an Asp.net 5 API application.
- create
IWritableOptions
interface andWritableOptions
class:using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.IO; namespace CoreMVC5Sample.Services { public interface IWritableOptions<out T> : IOptions<T> where T : class, new() { void Update(Action<T> applyChanges); } public class WritableOptions<T> : IWritableOptions<T> where T : class, new() { private readonly IWebHostEnvironment _environment; private readonly IOptionsMonitor<T> _options; private readonly IConfigurationRoot _configuration; private readonly string _section; private readonly string _file; public WritableOptions( IWebHostEnvironment environment, IOptionsMonitor<T> options, IConfigurationRoot configuration, string section, string file) { _environment = environment; _options = options; _configuration = configuration; _section = section; _file = file; } public T Value => _options.CurrentValue; public T Get(string name) => _options.Get(name); public void Update(Action<T> applyChanges) { var fileProvider = _environment.ContentRootFileProvider; var fileInfo = fileProvider.GetFileInfo(_file); var physicalPath = fileInfo.PhysicalPath; var jObject = JsonConvert.DeserializeObject<JObject>(File.ReadAllText(physicalPath)); var sectionObject = jObject.TryGetValue(_section, out JToken section) ? JsonConvert.DeserializeObject<T>(section.ToString()) : (Value ?? new T()); applyChanges(sectionObject); jObject[_section] = JObject.Parse(JsonConvert.SerializeObject(sectionObject)); File.WriteAllText(physicalPath, JsonConvert.SerializeObject(jObject, Formatting.Indented)); _configuration.Reload(); } } }
- The ServiceCollectionExtensions class:
using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace CoreMVC5Sample.Services { public static class ServiceCollectionExtensions { public static void ConfigureWritable<T>( this IServiceCollection services, IConfigurationSection section, string file = "appsettings.json") where T : class, new() { services.Configure<T>(section); services.AddTransient<IWritableOptions<T>>(provider => { var configuration = (IConfigurationRoot)provider.GetService<IConfiguration>(); var environment = provider.GetService<IWebHostEnvironment>(); var options = provider.GetService<IOptionsMonitor<T>>(); return new WritableOptions<T>(environment, options, configuration, section.Key, file); }); } } }
- The Locations class:
namespace CoreMVC5Sample.Models { public class Locations { public Locations() { Name = "default"; } public string Name { get; set; } } }
- The appsettings.json file: pay attention to the json format.
- Add the service in the startup file:
//required using CoreMVC5Sample.Models; // the Locations class is in the Models folder. // required using CoreMVC5Sample.Services; //the IWritableOptions is in the services folder. services.ConfigureWritable<Locations>(Configuration.GetSection("Locations"));
- The Controller and View page use the same code:
View:public class HomeController : Controller { private readonly IWritableOptions<Locations> _writableLocations; public HomeController(IWritableOptions<Locations> writableLocations) { _writableLocations = writableLocations; } public IActionResult Change(string value) { _writableLocations.Update(opt => { opt.Name = value; }); return RedirectToAction("Index"); }
<form asp-action="Change" method="post"> <input name="value" type="text"/> <input type="submit" value="submit"/> </form>
The result is like this:
You can try to use the above steps to create a new sample to test, if still meet the "value cannot be null" error, please create a simple sample to reproduce the problem.
If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.
Best regards,
Dillion