In dotnet mvc 5, how to inject a service into a view? - asp.net

In dotnet core mvc,i can inject a service into a view by using #inject directive,but how to do this in dotnet mvc 5.It seems that there is no #inject directive in mvc 5.

ASP.NET MVC 5 doesn't have #inject
This keyword only exists in ASP.NET Core.
Work-around using #model
However you can still inject a dependency in the controller and from the controller pass it to the model. #model and #Model can be used freely in the razor page/view.
MyController.cs:
public class MyController : Controller
{
public MyController(IMyDependency dependency)
{
_dependency = dependency;
}
public ActionResult Index()
{
View(new ViewModel
{
Dependency = _dependency
});
}
}
Razor.cshtml:
#model ViewModel
#if (Model.Dependency.CanView)
{
<!-- Put the code here -->
}
It also means you can put a IServiceProvider in the model and resolve directly in the view. However, in terms of separation of concerns, it's not the ideal.

Related

How do I instantiate class with dependency injection in .net core 5 or 6?

In a .net core 5 or 6 api, if I have a dependency:
services.AddScoped<IMyDependency>((svc) =>
{
return new MyDependency(appSettings.ConnectionStrings.MyDB);
});
and I have a class that uses the dependency:
public class MyClass
{
public MyClass(IMyDependency dependency)
{
...
}
}
How do I instantiate the class so that the dependency is injected into the constructor? In a controller, this is done for me when the controller is instantiated. How do I do it for my own classes?
I can use services.GetServices explicitly, but I think that is frowned upon.
I can use services.GetServices explicitly, but I think that is frowned
upon.
Yes, this is the documented way to do it:
Services and their dependencies within an ASP.NET Core request are
exposed through HttpContext.RequestServices.
Source
HttpContext.RequestServices is of type IServiceProvider which offers the GetService method.
But it is also adviced not to do it:
Avoid using the service locator pattern. For example, don't invoke
GetService to obtain a service instance when you can use DI instead
Source
This won't be done by default. If you are trying to use something that is injected into the request pipeline, you would have to pass that injected value in the controller and pass it on to the constructor.
public class HomeController : Controller
{
private IMyDependency _dependency;
public HomeController(IMyDependency dependency)
{
_dependency = dependency;
}
[Route("")]
public IActionResult Index()
{
MyClass myclass = new MyClass(_dependency);
....
return View(myclass.stuff);
}
Add the class and its dependencies to the service collection, then inject it into your methods or use the IServiceProvider GetRequiredService
eg.
public class MyClass
{
public MyClass(IMyDependency dependency)
{
...
}
}
...
startup.cs
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<MyClass>()
...
[Route("home")]
public IActionResult Index([FromServices]MyClass myClass)
{
var result = myClass.DoStuff();
....
return View(result);
}

how to inject dependency in _viewimports file

I am trying to generate a generic BaseViewPage in asp.net Core to access Current User's Identities.
For this purpose I created a BaseViewPage.cs file -
public abstract class BaseViewPage<TModel> : RazorPage<TModel>
{
private static ClaimsPrincipal principal;
public BaseViewPage(IPrincipal _principal)
{
principal = _principal as ClaimsPrincipal;
}
}
As you can see in the constructor here I am injecting a dependency of IPrincipal type so that it gets defined at run time.
Now time to inherit it in _viewimports.cshtml file to use current user in all the view pages as below -
#using TWCStore
#inherits MyStore.Helpers.BaseViewPage<TModel>
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Whenever I try to use any property in my view - ProductCategories.cshtml the error invokes here
Severity Code Description Project File Line Suppression State
Error CS7036 There is no argument given that corresponds to the
required formal parameter '_principal' of
'BaseViewPage.BaseViewPage(IPrincipal)' MyStore D:\Projects\TWCStore\TWCStore\Views\Store\ProductCategories.cshtml 1 Active
I am assuming when I am injecting the dependency then it wants me to put the IPrincipal as Dependency here too -
#inherits MyStore.Helpers.BaseViewPage<TModel>
How do I inject this dependency in here ?
Its late I am writing solution (actually an alternate) to this problem here.
So I followed dependency injection (sorry for now forgetting the help link).
Here is what I did -
Create an Interface
public interface IAppUserAccessor
{
int MemberId { get; }
}
Resolver class
public class AppUserAccessor : IAppUserAccessor
{
private readonly MyContext _context;
public AppUserAccessor(MyContext context)
{
_context = context;
}
int IAppUserAccessor.MemberId
{
get
{
return _context.Member.MemberId;
}
}
}
Register the service in StartUp.cs under 'ConfigureServices' section
services.AddTransient<IAppUserAccessor, AppUserAccessor>();
Inject in _viewimports.cshtml
#using MyApplication
#inject MyApplication.Helpers.IAppUserAccessor AppUserAccessor
Now this is accessible in every view as -
#AppUserAccessor.MemberId

Autofac lazy property injection

I'm trying to inject business logic implementations into web API base controller. Somehow property in base controller is always null.
Also how can I do lazy injection?
Startups.cs
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<ViewBusinessLogic>().As<IViewBusinessLogic>().
PropertiesAutowired();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return container.Resolve<IServiceProvider>();
}
Interface, implementation and base controller:
public interface IViewBusinessLogic
{
IEnumerable<dynamic> GetView(Guid viewId);
}
public class ViewBusinessLogic : BusinessLogic, IViewBusinessLogic
{
public IEnumerable<dynamic> GetView(Guid viewId)
{
return new List<dynamic>
{
new { Test = "Test1" },
new { Test = "Test2" }
};
}
}
public abstract class BaseController : Controller
{
public IViewBusinessLogic ViewBusinessLogic { get; }
}
Controllers aren't resolved by the DI framework by default. You need to add AddControllerAsServices to have them be resolved by the DI of your choice.
From this GitHub issue:
Hi,
Maybe I'm wrong but as I tested deeply (and checked Mvc source code), Controllers are not resolved from IServiceProvider, but only constructor arguments of them are resolved from IServiceProvider.
Is that by design? I'm very suprised. Because, I'm using a different DI framework which supports property injection. And I can not use property injection since Controller instances are not requested from IServiceProvider.
Have you added AddControllersAsServices in your Startup (https://github.com/aspnet/Mvc/blob/ab76f743f4ee537939b69bdb9f79bfca35398545/test/WebSites/ControllersFromServicesWebSite/Startup.cs#L37)
The example above quoted for future reference.
public void ConfigureServices(IServiceCollection services)
{
var builder = services
.AddMvc()
.ConfigureApplicationPartManager(manager => manager.ApplicationParts.Clear())
.AddApplicationPart(typeof(TimeScheduleController).GetTypeInfo().Assembly)
.ConfigureApplicationPartManager(manager =>
{
manager.ApplicationParts.Add(new TypesPart(
typeof(AnotherController),
typeof(ComponentFromServicesViewComponent),
typeof(InServicesTagHelper)));
manager.FeatureProviders.Add(new AssemblyMetadataReferenceFeatureProvider());
})
// This here is important
.AddControllersAsServices()
.AddViewComponentsAsServices()
.AddTagHelpersAsServices();
services.AddTransient<QueryValueService>();
services.AddTransient<ValueService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
As for the second part of your question: I don't think it's possible to have lazy instantiation via IoC container at all. Best fit for you is to create a factory class and inject the factory rather than the concrete service.
But usually you don't need lazy instantiation anyways, the instantiation of services should be fast. If it's not, you probably doing some funky stuff in the constructor (connecting somewhere, or doing other long running operations), which is an anti-pattern.

Create/Get DefaultHtmlGenerator from MVC Controller

I am trying to create(Or get an instance of it somehow) for Microsoft.AspNet.Mvc.Rendering.DefaultHtmlGenerator inside my MVC6 controller method
I wanted to generate the html for validation for my Model my self inside my controller of asp.net mvc. My issue is where to get the constructor data for DefaultHtmlGenerator like antiforgery, metadataProvider..etc
[HttpGet]
public IActionResult GetMarkup()
{
// IHtmlGenerator ge = this.CurrentGenerator();
IHtmlGenerator ge = new DefaultHtmlGenerator(params);
var tag= ge.GetClientValidationRules(params)
}
here is the a link about the HtmlGenerator class
DefaultHtmlGenerator
Since MVC 6 is based on dependency injection, all you have to do is require IHtmlGenerator in your constructor, and the DI container will automatically fill in all of the dependencies of DefaultHtmlGenerator (provided that is what is setup in your DI configuration).
public class HomeController : Controller
{
private readonly IHtmlGenerator htmlGenerator;
public HomeController(IHtmlGenerator htmlGenerator)
{
if (htmlGenerator == null)
throw new ArgumentNullException("htmlGenerator");
this.htmlGenerator = htmlGenerator;
}
public IActionResult GetMarkup()
{
// Use the HtmlGenerator as required.
var tag = this.htmlGenerator.GetClientValidationRules(params);
return View();
}
}
That said, it appears that the GetClientValidationRules method is only designed to work within a view, since it accepts ViewContext as a parameter. But this does answer the question that you asked.

MVC 6 #inherit RazorPage

I am trying to migrate an MVC 5 Application to ASP.NET 5 MVC 6 (Beta 7).
Having problems when using the #inherits and #model directive together.
Works fine when they are used separately.
In my _ViewImports i added the #inherits directive to use a base page with some custom user properties.
public abstract class BaseViewPage<TModel> : RazorPage<TModel>
{
protected MyPrincipal AppUser
{
get
{
return new MyPrincipal(this.User as ClaimsPrincipal);
}
}
}
_ViewImports.cshttml
#inherits CommonWeb.BaseViewPage<TModel>
#addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
And then i can go AppUser. in all my views.
This works if i dont use a strongly typed view. If i add the #model directive in any view the inherited view page goes away.
Help appreciated
Update:
I did this successfully by using a custom pageBaseType in the web.config in prior versions.
Workaround.
public class ViewHelper
{
ViewContext _context;
public ViewHelper(ViewContext context)
{
_context = context;
}
public MyPrincipal AppUser
{
get
{
return new MyPrincipal(_context.HttpContext.User as ClaimsPrincipal);
}
}
public string ControllerName
{
get
{
return _context.RouteData.Values["controller"].ToString();
}
}
}
View:
#{ var viewHelper = new ViewHelper(ViewContext);}
A way to achieve this for all views?
There is a better way in MVC 6, which now supports injecting dependencies on the views with the #inject directive. (The directive #inject IFoo Foo allows you to use in your view a property named Foo of type IFoo)
Create a new interface IAppUserAccessor for getting your app user, for example:
public interface IAppUserAccessor
{
MyPrincipal GetAppUser();
}
Create a class AppUserAccessor implementing it:
public class AppUserAccessor : IAppUserAccessor
{
private IHttpContextAccessor httpContextProvider;
public AppUserAccessor(IHttpContextAccessor httpContextProvider)
{
this.httpContextProvider = httpContextProvider;
}
public MyPrincipal GetAppUser()
{
return new MyPrincipal (
httpContextProvider.HttpContext.User as ClaimsPrincipal);
}
}
Register the new interface in the services container by adding a new entry in the ConfigureServices method of Startup.cs:
services.AddTransient<IAppUserAccessor, AppUserAccessor>();
Finally use the #inject directive to inject the IAppUserAccessor in your views. If you add the directive in ViewImports.cshtml then it will be available on every view.
#inject WebApplication4.Services.IAppUserAccessor AppUserAccessor
With all the pieces above you can now just use it on your view(s):
#AppUserAccessor.GetAppUser()
Update
If you need to inspect the route values, like the controller name, you can inject an IActionContextAccessor into your class and use it as follows:
public AppUserAccessor(IHttpContextAccessor httpContextProvider, IActionContextAccessor actionContextAccessor)
{
this.httpContextProvider = httpContextProvider;
this.actionContextAccessor = actionContextAccessor;
}
...
public string ControllerName
{
get { return actionContextAccessor.ActionContext.RouteData.Values["controller"].ToString(); }
}
Of course, that doesn't look like an AppUserAccessor anymore and smells like it has different responsabilities. At the very least it needs a more appropriate name :)
I would double check what do I need the controller name for. There might be a better way to accomplish your objective. (For example, if you need it for generating new links/urls you might just use an IUrlHelper)
Accessing ViewContext
Looks like beta8 has added support for injecting the ViewContext, although the implementation details may change before RC. See this question

Resources