ASPNET CORE - Can I add a ControllerModel in a IApplicationModelConvention? - asp.net-core

I'm trying to add controllers dynamically, the catch is that their actions are also dynamic, so I can't simply use a generic controller.
I dug through msdn and found that overriding the method Apply#IApplicationModelConvention seems to be my best option. However, the controller I add is never found by the routing system.
This is what I tried:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(opts =>
{
opts.Conventions.Add(new ApplicationDescription("mdesc"));
opts.Conventions.Add(new ControllerDescription("mctrl"));
})
...
}
public class ApplicationDescription : IApplicationModelConvention
{
private readonly string _description;
public ApplicationDescription(string description)
{
_description = description;
}
public void Apply(ApplicationModel application)
{
var basec = application.Controllers[0];
var cm = new ControllerModel(typeof(MyController).GetTypeInfo(), basec.Attributes);
application.Controllers.Add(cm);
application.Properties["description"] = _description;
}
}
Source: https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/application-model?view=aspnetcore-2.0

Related

Define class dynamically with Service Locator - Asp.Net Core

I am working with Asp.Net Core application. I have two classes namely Online and Offline. I have created interface and defined the methods in these two classes. Based on the need I have to connect to anyone of these two classes.
Previously when I worked in Asp.Net MVC, I have used unity container and Service Locator to specify the class name in XML file for invoking the class dynamically (between online and offline).
Now I want to implement the same with Asp.Net core. But I am not sure how to specify the class name outside for method invocation. Kindly help.
Thanks
In .net core dependency injection is in built. You don't need unity or any other any more.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0
You can achieve what you want by using a little tweak.
//// classes
public interface IFileUploadContentProcess
{
IEnumerable<StoreOrder> ProcessUploads(IFormFile file);
}
public class ProcessExcelFiles : IFileUploadContentProcess
{
public IEnumerable<StoreOrder> ProcessUploads(IFormFile file)
{
throw new NotImplementedException();
}
}
public class ProcessCsvFiles : IFileUploadContentProcess
{
public IEnumerable<StoreOrder> ProcessUploads(IFormFile file)
{
throw new NotImplementedException();
}
}
//// register it
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddTransient<IStoreOrderService, StoreOrderService>();
services.AddTransient<ProcessExcelFiles>();
services.AddTransient<ProcessCsvFiles>();
// Add resolvers for different sources here
services.AddTransient<Func<string, IFileUploadContentProcess>>(serviceProvider => key =>
{
return key switch
{
"xlsx" => serviceProvider.GetService<ProcessExcelFiles>(),
_ => serviceProvider.GetService<ProcessCsvFiles>(),
};
});
}
//use it
public class StoreOrderService : IStoreOrderService
{
private readonly Func<string, IFileUploadContentProcess> _fileUploadContentProcess;
public StoreOrderService(Func<string, IFileUploadContentProcess> fileUploadContentProcess)
{
_fileUploadContentProcess = fileUploadContentProcess;
}
public async Task<IEnumerable<StoreOrder>> UploadStoreOrdersAsync(IFormFile file)
{
//// passing csv to process csv type(default), if xlsx, pass xlsx
var records = _fileUploadContentProcess("csv").ProcessUploads(file);
return records;
}
}
After lot of brainstroming, I found the below solution
Create a class for ServiceLocator
public class ServiceLocator
{
private ServiceProvider _currentServiceProvider;
private static ServiceProvider _serviceProvider;
public ServiceLocator(ServiceProvider currentServiceProvider)
{
_currentServiceProvider = currentServiceProvider;
}
public static ServiceLocator Current
{
get
{
return new ServiceLocator(_serviceProvider);
}
}
public static void SetLocatorProvider(ServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public object GetInstance(Type serviceType)
{
return _currentServiceProvider.GetService(serviceType);
}
public TService GetInstance<TService>()
{
return _currentServiceProvider.GetService<TService>();
}
}
Step 2: Create interface and inherit in the classes and define the interface methods
Step 3: Define class name in appSettings.json and read the values in startup.cs
public void ConfigureServices(IServiceCollection services)
{
//reading from appSettings.json
string strClassName = Configuration["DependencyInjection:className"];
if (strClassName == "OnlineData")
services.AddTransient<<<InterfaceName>>, <<OnlineClassName>>>();
if (strClassName == "OfflineData")
services.AddTransient<<<InterfaceName>>, <<OfflineClassName>>>();
}
Step 4: Create object for the dynamic class inside controller/action method
InterfaceNamemyService = ServiceLocator.Current.GetInstance<>();

How to inject HttpContextAccessor directly from ConfigureServices method

My goal is to set a username string based on the environment I'll be working on that must be:
an arbitrary string for the development and staging environment
the HttpContext.User.Identity.Name in production.
This is because I have to be able to simulate different kind of users and I achieve this by calling the FindByIdAsync method on my custom implementation of UserIdentity using this username string as a parameter, like this:
public class HomeController : Controller
{
UserManager<AppUser> userManager;
AppUser connectedUser;
public HomeController(UserManager<AppUser> usrMgr, IContextUser ctxUser)
{
connectedUser = usrMgr.FindByNameAsync(ctxUser.ContextUserId).Result;
}
}
I started creating three appsettings.{environment}.json file for the three usual development, staging and production environments; development and staging .json files both have this configuration:
...
"Data": {
...
"ConnectedUser" : "__ADMIN"
}
...
while the production environment configuration file doesn't have this key.
I have created a simple interface
public interface IContextUser
{
public string ContextUserId { get; }
}
and its implementation:
public class ContextUser : IContextUser
{
string contextUser;
IHttpContextAccessor contextAccessor;
public ContextUser(IHttpContextAccessor ctxAccessor, string ctxUser = null)
{
contextUser = ctxUser;
contextAccessor = ctxAccessor;
}
public string ContextUserId => contextUser ?? contextAccessor.HttpContext.User.Identity.Name;
}
Now, I thought of simply configuring the ConfigureServices method in the Startup class:
public void ConfigureServices(IServiceCollection services)
{
// --- add other services --- //
string ctxUser = Configuration["Data:ConnectedUser"];
services.AddSingleton(service => new ContextUser( ??? , ctxUser ));
}
but it needs an IHttpContextAccessor object, that seems unavailable at this stage of the application. How can I solve this issue?
The HttpContextAccessor makes use of a static AsyncLocal<T> property under the covers, which means that any HttpContextAccessor implementation will access the same data. This means you can simply do the following:
services.AddSingleton(c => new ContextUser(new HttpContextAccessor(), ctxUser));
// Don't forget to call this; otherwise the HttpContext property will be
// null on production.
services.AddHttpContextAccessor();
If you find this too implicit, or don't the HttpContextAccessor implementation from breaking in the future, you can also do the following:
var accessor = new HttpContextAccessor();
services.AddSingleton<IHttpContextAccessor>(accessor);
services.AddSingleton(c => new ContextUser(accessor, ctxUser));
Or you can "pull out" the registered instance out of the ServiceCollection class:
services.AddHttpContextAccessor();
var accessor = (IHttpContextAccessor)services.Last(
s => s.ServiceType == typeof(IHttpContextAccessor)).ImplementationInstance;
services.AddSingleton(c => new ContextUser(accessor, ctxUser));
What I find a more pleasant solution, however, especially from a design perspective, is to split the ContextUser class; it currently seems to implement two different solutions. You can split those:
public sealed class HttpContextContextUser : IContextUser
{
private readonly IHttpContextAccessor accessor;
public HttpContextContextUser(IHttpContextAccessor accessor) =>
this.accessor = accessor ?? throw new ArgumentNullException("accessor");
public string ContextUserId => this.accessor.HttpContext.User.Identity.Name;
}
public sealed class FixedContextUser : IContextUser
{
public FixedContextUser(string userId) =>
this.ContextUserId = userId ?? throw new ArgumentNullException("userId");
public string ContextUserId { get; }
}
Now, depending on the environment you're running in, you register either one of them:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
if (this.Configuration.IsProduction())
{
services.AddSingleton<IContextUser, HttpContextContextUser>();
}
else
{
string ctxUser = Configuration["Data:ConnectedUser"];
services.AddSingleton<IContextUser>(new FixedContextUser(ctxUser));
}
}

Extending ResourceManagerStringLocalizer functionality

I am fairly new to .net core and I want to implement localization in my application. The built in ResourceManagerStringLocalizer service satisfies almost all my requirements and works great out of the box, but I want to add some custom functionality to it, without copy pasting all of the class' code and adding some to it in my own localizer. Currently I have tried deriving from the class in my own Localizer and injecting that as my IStringLocalizer:
services.AddTransient<IStringLocalizer<MyApp.SharedResource>, MyLocalizer<MyApp.SharedResource>>();
...
public class MyLocalizer<T> : ResourceManagerStringLocalizer, IStringLocalizer<T> {
public MyLocalizer(ResourceManager resourceManager, IResourceStringProvider resourceStringProvider, string baseName, IResourceNamesCache resourceNamesCache, ILogger logger) : base(resourceManager, resourceStringProvider, baseName, resourceNamesCache, logger) {
}
public override LocalizedString this[string name] {
get {
//do some custom functionality
return base[name];
}
}
public override LocalizedString this[string name, params object[] arguments] {
get {
//do some custom functionality
return base[name, arguments];
}
}
}
But this gives the following exception when trying to use it:
System.InvalidOperationException: 'Unable to resolve service for type 'System.Resources.ResourceManager' while attempting to activate 'MyApp.Services.MyLocalizer`1[MyApp.SharedResource]'.'
My question is, what's the best way to this? And whats best practice for doing things like this? I feel like this sort of stuff may come up more often in the future of this application and all help will be appreciated.
You don't have to override the built-in localizer, just implement your custom one:
public class MyLocalizer
{
private readonly IStringLocalizer _localizer;
public MyLocalizer(IStringLocalizerFactory factory)
{
var type = typeof(MyResourceType);
var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
_localizer = factory.Create("MyResourceType", assemblyName.Name);
}
public LocalizedString this[string name]
{
get
{
// ...
return _localizer[name];
}
}
public LocalizedString this[string name, params object[] arguments]
{
get
{
// ...
return _localizer[name, arguments];
}
}
}
Then you can simple inject it to the views:
#inject MyLocalizer _loc;
<h1>#_loc["Hellow"]</h1>
or to the backend:
public class HomeController : Controller
{
private readonly MyLocalizer _loc;
public HomeController(MyLocalizer loc)
{
_loc = loc;
}
public IActionResult Index()
{
var msg = _loc["Welcome"];
//...
}
}

MockingKernel and Received throws an NotASubstituteException

I'm using NSubstituteMockingKernel in order to build all my dependencies of my library classes. I've been struggling to solve a trouble for a week and I'm really exhausted. I need some help.
This is my class:
class Core : ICoreService, ICore {
private ICoreConfiguration configuration;
Core(ICoreconfiguration configuration) {
this.configuration = configuration;
}
override ICoreService.ParentMethod() { //ICoreService implementation
foreach (Item item in this.configuration.Items)
this.ChildMethod();
}
virtual ChildMethod() {
//do something
}
}
ICoreService is:
interface ICoreService {
void ParentMethod();
}
ICore is:
interface ICore {
ICoreConfiguration Configuration { get; }
}
ICoreConfiguration is:
interface ICoreConfiguration {
IEnumerable<Item> Items { get; }
}
My test is:
[TestFixture]
public class UsersManagementTests
{
private readonly NSubstituteMockingKernel IoCKernel;
public UsersManagementTests()
{
this.IoCKernel = new NSubstituteMockingKernel();
}
[SetUp]
public void SetUp()
{
this.IoCKernel.Reset();
}
[Test(Description = "Configured Users are well loaded on Kernel")]
public void InitializationWithUsersTest()
{
//Setup Data
Item item = Item.Create("item1");
IEnumerable<Item> items = new List<Item>() { item };
//Setup Mocks
this.IoCKernel
.Get<ICoreConfiguration>()
.Items
.Returns(items);
Core core = this.IoCKernel.Get<Core>();
//Act
kernel.ParentMethod();
//Assert
IEnumerable<NSubstitute.Core.ICall> calls = kernel.ReceivedCalls(); // ((((((1))))))
kernel.Received(1).ChildMethod(); // ((((((2))))))
}
}
When ((((((1)))))) or ((((((2)))))) are reached, I'm getting this NSubstitute.Exceptions.NotASubstituteException exception message now on last line:
NSubstitute extension methods like .Received() can only be called on objects created using Substitute.For() and related methods.
As you can see I'm trying to test ChildMethod method is reached once at least. ChildMethod must be called according to my Core.Kernel implementation.
I will really appreciate some help.
Thanks.

Managing RavenDB IDocumentSession lifecycles with StructureMap for NServiceBus and MVC

I am using NServiceBus v4.3, MVC4, RavenDB 2.5 and StructureMap 2.6.4 in our solution.
I am having a similar issue under StructureMap to that described in this question's responses where I require different lifecycles for the MVC Controller and NServiceBus Handler use of RavenDB's IDocumentSession in my Web project.
Specifically in my case what happens is that if I use the HybridHttpOrThreadLocalScoped (as the above answer suggests for Windsor) lifecycle the sessions are not properly disposed of and I soon hit the 30 transaction limit error. If I use the HttpContext lifecycle my NSB event Handlers in the Web project do not get called.
In my Controllers the session is wrapped in a unit of work applied via an MVC ActionFilter. I also use the UoW within the Handlers as my Registry is wired up to retrieve the session from the UoW. The code is as such:
RavenDbWebRegistry.cs
public sealed class RavenDbWebRegistry : Registry
{
public RavenDbWebRegistry()
{
// register RavenDB document store
ForSingletonOf<IDocumentStore>().Use(() =>
{
var documentStore = new DocumentStore
{
ConnectionStringName = "RavenDB",
Conventions =
{
IdentityPartsSeparator = "-",
JsonContractResolver = new PrivatePropertySetterResolver(),
},
};
documentStore.Initialize();
return documentStore;
});
For<IDocumentSession>().HybridHttpOrThreadLocalScoped().Add(ctx =>
{
var uow = (IRavenDbUnitOfWork)ctx.GetInstance<IUnitOfWork>();
return uow.DocumentSession;
});
For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<WebRavenDbUnitOfWork>();
}
}
Example of Web project Handler:
public class SiteCreatedEventHandler : IHandleMessages<ISiteCreatedEvent>
{
public IBus Bus { get; set; }
public IUnitOfWork Uow { get; set; }
public IDocumentSession DocumentSession { get; set; }
public void Handle(ISiteCreatedEvent message)
{
try
{
Debug.Print(#"{0}{1}", message, Environment.NewLine);
Uow.Begin();
var site = DocumentSession.Load<Site>(message.SiteId);
Uow.Commit();
//invoke Hub and push update to screen
var context = GlobalHost.ConnectionManager.GetHubContext<AlarmAndNotifyHub>();
//TODO make sure this SignalR function is correct
context.Clients.All.displayNewSite(site, message.CommandId);
context.Clients.All.refreshSiteList();
}
catch (Exception ex)
{
Uow.Rollback();
}
}
}
Usage of ActionFilter:
[RavenDbUnitOfWork]
public ViewResult CreateNew(int? id)
{
if (!id.HasValue || id.Value <= 0)
return View(new SiteViewModel { Guid = Guid.NewGuid() });
var targetSiteVm = MapSiteToSiteViewModel(SiteList(false)).FirstOrDefault(s => s.SiteId == id.Value);
return View(targetSiteVm);
}
WebRegistry (that sets up NSB in my MVC project)
public sealed class WebRegistry : Registry
{
public WebRegistry()
{
Scan(x =>
{
x.TheCallingAssembly();
x.Assembly("IS.CommonLibrary.ApplicationServices");
x.LookForRegistries();
});
IncludeRegistry<RavenDbWebRegistry>();
FillAllPropertiesOfType<IUnitOfWork>();
FillAllPropertiesOfType<IDocumentSession>();
FillAllPropertiesOfType<StatusConversionService>();
FillAllPropertiesOfType<IStateRepository<TieState>>();
FillAllPropertiesOfType<IStateRepository<DedState>>();
FillAllPropertiesOfType<ITieService>();
FillAllPropertiesOfType<IDedService>();
FillAllPropertiesOfType<IHbwdService>();
//NServiceBus
ForSingletonOf<IBus>().Use(
NServiceBus.Configure.With()
.StructureMapBuilder()
.DefiningCommandsAs(t => t.Namespace != null && t.Namespace.EndsWith("Command"))
.DefiningEventsAs(t => t.Namespace != null && t.Namespace.EndsWith("Event"))
.DefiningMessagesAs(t => t.Namespace == "Messages")
.RavenPersistence("RavenDB")
.UseTransport<ActiveMQ>()
.DefineEndpointName("IS.Argus.Web")
.PurgeOnStartup(true)
.UnicastBus()
.CreateBus()
.Start(() => NServiceBus.Configure.Instance
.ForInstallationOn<Windows>()
.Install())
);
//Web
For<HttpContextBase>().Use(() => HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current));
For<ModelBinderMappingDictionary>().Use(GetModelBinders());
For<IModelBinderProvider>().Use<StructureMapModelBinderProvider>();
For<IFilterProvider>().Use<StructureMapFilterProvider>();
For<StatusConversionService>().Use<StatusConversionService>();
For<ITieService>().Use<TieService>();
For<IDedService>().Use<DedService>();
For<IHbwdService>().Use<HbwdService>();
For<ISiteService>().Use<SiteService>();
IncludeRegistry<RedisRegistry>();
}
I have tried configuring my Registry using every possible combination I can think of to no avail.
Given that the StructureMap hybrid lifecycle does not work as I would expect, what must I do to achieve the correct behaviour?
Is the UoW necessary/beneficial with RavenDB? I like it (having adapted it from my earlier NHibernate UoW ActionFilter) because of the way it manages the lifecycle of my sessions within Controller Actions, but am open to other approaches.
What I would ideally like is a way to - within the Web project - assign entirely different IDocumentSessions to Controllers and Handlers, but have been unable to work out any way to do so.
Firstly, RavenDB already implements unit of work by the wrapping IDocumentSession, so no need for it. Opening a session, calling SaveChanges() and disposing has completed the unit of work
Secondly, Sessions can be implemented in a few ways for controllers.
The general guidance is to set up the store in the Global.asax.cs. Since there is only 1 framework that implements IDocumentSession - RavenDB, you might as well instantiate it from the Global. If it was NHibernate or Entity Framework behind a repository, I'd understand. But IDocumentSession is RavenDB specific, so go with a direct initialization in the Application_Start.
public class Global : HttpApplication
{
public void Application_Start(object sender, EventArgs e)
{
// Usual MVC stuff
// This is your Registry equivalent, so insert it into your Registry file
ObjectFactory.Initialize(x=>
{
x.For<IDocumentStore>()
.Singleton()
.Use(new DocumentStore { /* params here */ }.Initialize());
}
public void Application_End(object sender, EventArgs e)
{
var store = ObjectFactory.GetInstance<IDocumentStore>();
if(store!=null)
store.Dispose();
}
}
In the Controllers, add a base class and then it can open and close the sessions for you. Again IDocumentSession is specific to RavenDB, so dependency injection doesn't actually help you here.
public abstract class ControllerBase : Controller
{
protected IDocumentSession Session { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext context)
{
Session = ObjectFactory.GetInstance<IDocumentStore>().OpenSession();
}
protected override void OnActionExecuted(ActionExecutedContext context)
{
if(this.IsChildAction)
return;
if(content.Exception != null && Session != null)
using(context)
Session.SaveChanges();
}
}
Then from there, inherit from the base controller and do your work from there:
public class CustomerController : ControllerBase
{
public ActionResult Get(string id)
{
var customer = Session.Load<Customer>(id);
return View(customer);
}
public ActionResult Edit(Customer c)
{
Session.Store(c);
return RedirectToAction("Get", c.Id);
}
}
Finally, I can see you're using StructureMap, so it only takes a few basic calls to get the Session from the DI framework:
public class SiteCreatedEventHandler : IHandleMessages<ISiteCreatedEvent>
{
public IBus Bus { get; set; }
public IUnitOfWork Uow { get; set; }
public IDocumentSession DocumentSession { get; set; }
public SiteCreatedEventHandler()
{
this.DocumentSession = ObjectFactory.GetInstance<IDocumentStore>().OpenSession();
}
public void Handle(ISiteCreatedEvent message)
{
using(DocumentSession)
{
try
{
Debug.Print(#"{0}{1}", message, Environment.NewLine);
///// Uow.Begin(); // Not needed for Load<T>
var site = DocumentSession.Load<Site>(message.SiteId);
//// Uow.Commit(); // Not needed for Load<T>
// invoke Hub and push update to screen
var context = GlobalHost.ConnectionManager.GetHubContext<AlarmAndNotifyHub>();
// TODO make sure this SignalR function is correct
context.Clients.All.displayNewSite(site, message.CommandId);
context.Clients.All.refreshSiteList();
}
catch (Exception ex)
{
//// Uow.Rollback(); // Not needed for Load<T>
}
}
}