i use ninject to bind my interfaces to my repository as you can see :
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<CMSDataContext>().To<CMSDataContext>().InRequestScope();
kernel.Bind<IUserRepository>().To<UserRepository>().InRequestScope();
kernel.Bind<INewsRepository>().To<NewsRepository>().InRequestScope();
kernel.Bind<IConfigurationRepository>().To<ConfigurationRepository>().InRequestScope();
}
For example you can see here the structure of home controller :
public class HomeController : Controller
{
//
// GET: /fa/Home/
private IConfigurationRepository _configurationRepository;
public HomeController(IConfigurationRepository configurationRepository)
{
_configurationRepository = configurationRepository;
}
public ActionResult Index()
{
ViewBag.Configuration = _configurationRepository.GetConfiguration().First();
return View();
}
}
But i need to call an interface in my shared view i mean masterpage as you can see here:
<head>
#{
IConfigurationRepository _iconfigurationRepository;
}
<!-- Basic -->
<title>#ViewBag.Configuration.Title</title>
<!-- Define Charset -->
<meta charset="utf-8" />
My question is how can i bind this interface in view to its repository i mean configurationRepository using ninject ?
If you need to resolve a dependency in your view , you can use the dependencyResolver.
In your razor view:
#{
var config = DependencyResolver.Current.GetService<IConfigurationRepository >();
}
In this case, if ninject can resolve constructor-parameters then ninject is the current dependency resolver and you can use in views, filters, controllers,etc.
Related
I have an application that uses mvvm pattern with Community Toolkit features. I am trying to use Dependency Injection to use an ApiService with its interface in my viewmodels, but after following the steps described here, I can't access to App.xaml Resources (specifically colors), the intellisense works when I am writing code in XAML, but it doesn´t work after running. It is important to notice that I was using colors from resources correctly before trying to use Dependency Injection and changing the ViewModel - View linking method to the one described here, but I was unable to use ApiService.
Here is my code.
App.xaml.cs (Login page is my first page):
public App(LoginPage page)
{
InitializeComponent();
MainPage = page;
}
LoginPage.xaml.cs
public partial class LoginPage : ContentPage
{
public LoginPage(LoginPageViewModel loginPageViewModel)
{
BindingContext = loginPageViewModel;
InitializeComponent();
}
}
LoginPageViewModel
public LoginPageViewModel(IApiService apiService)
{
_apiService = apiService;
Opacity = 1f;
IsEnabled = true;
IsRunning = false;
}
LoginPage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="NewScholarApp.Views.LoginPage"
Title="LoginPage"
xmlns:vm="clr-namespace:NewScholarApp.ViewModels"
x:DataType="vm:LoginPageViewModel"
BackgroundColor="{StaticResource GreenSchool}">
MauiProgam.cs
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>().UseMauiCommunityToolkit().ConfigureViewModels().ConfigureServices().ConfigureViews()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
//builder.Services.AddSingleton<ApiService>();
return builder.Build();
}
RegisterViews and ViewModels
public static MauiAppBuilder ConfigureViews(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddTransient<LoginPage>();
return mauiAppBuilder;
}
public static class RegisterViewModels
{
public static MauiAppBuilder ConfigureViewModels(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddSingleton<LoginPageViewModel>();
return mauiAppBuilder;
}
}
Open issue: https://github.com/dotnet/maui/issues/11485
This question is related to Cannot use StaticResources for resources in App.xaml. IndianaGary's comment indicated that you could try a DynamicResource.
I want to be able to inject ViewModels into the Views in my Windows Phone 8 application using Autofac as my IoC container. How do I go about doing this? I have looked at the Caliburn.Micro framework, but I'd like to use something simpler.
Precisely for this purpose I have created a small demo application. It defines a ViewModelLocator class:
public class ViewModelLocator
{
private readonly IContainer container;
public ViewModelLocator()
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<MainViewModel>();
containerBuilder.RegisterType<ItemViewModel>();
this.container = containerBuilder.Build();
}
public MainViewModel MainViewModel
{
get
{
return this.container.Resolve<MainViewModel>();
}
}
public ItemViewModel ItemViewModel
{
get
{
return this.container.Resolve<ItemViewModel>();
}
}
}
To use this class from your views, you have to add it to your application's resources. You do this by modifying the Application.Resources section in App.xaml:
<Application.Resources>
<local:ViewModelLocator xmlns:local="clr-namespace:AutofacWP8DependencyInjectionDemo" x:Key="ViewModelLocator"/>
</Application.Resources>
Now you will be able to inject the view model in the view. Just have the view point to the DataContext. To reference the MainViewModel as the DataContext simply add the following to your view:
DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}"
You can see that it sets the DataContext to the MainViewModel property of the ViewModelLocator class, which uses Autofac to create the MainViewModel instance using dependency injection.
You can find the source here: https://github.com/ErikSchierboom/autofacwp8dependencyinjectiondemo.git
I want to be able to change the _Layout.cshtml view based on a setting in my database.
I understand that it is probably done in the _ViewStart.cshml view.
I am using EF 4.2 and want to adapt a solution that will not break any design pattern.
Not sure how to go about doing this in MVC.
In web forms, I could easily do this in the code-behind for the masterpage.
I am doing something like this in my base controller:
public abstract class BaseController : Controller
{
private IUserRepository _userRepository;
protected BaseController()
: this(
new UserRepository())
{
}
public BaseController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
I have looked at FunnelWeb source as well but I am not quite getting how they are injecting things..
Add this code to in the RegisterBundles method of the BundleConfig class. Note that I am creating a separate bundle for each css so that I don't render each css to the client. I can pick which bundle I want to render in the HEAD section of the shared _Layout.cshtml view.
bundles.Add(new StyleBundle("~/Content/Ceruleancss").Include(
"~/Content/bootstrapCerulean.min.css",
"~/Content/site.css"));
bundles.Add(new StyleBundle("~/Content/defaultcss").Include(
"~/Content/bootstrap.min.css",
"~/Content/site.css"));
Then put some logic in the shared_Layout.cshtml to render the appropriate bundle. Since this layout view fires for every page, this is a good place to put it.
I think this approach could be used for branding if you support multiple corps for your app. It could also be used to provide a custom style by user I suppose.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>#ViewBag.Title - Contoso University</title>
#{
if (HttpContext.Current.User.Identity.Name == "MARK")
{
#Styles.Render("~/Content/defaultcss");
}
else
{
#Styles.Render("~/Content/Ceruleancss");
}
}
Old Question but for anyone coming across this question here is a nice solution using Action Filters Attributes
public class LoadUserLayoutAttribute : ActionFilterAttribute
{
private readonly string _layoutName;
public LoadUserLayoutAttribute()
{
_layoutName = MethodToGetLayoutNameFromDB();
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var result = filterContext.Result as ViewResult;
if (result != null)
{
result.MasterName = _layoutName;
}
}
}
and then, you can add an attribute to your base controller (or action) with this custom attribute:
[LoadUserLayout]
public abstract class BaseController : Controller
{
...
}
In the past I've stuck common properties, such as the current user, onto ViewData/ViewBag in a global fashion by having all Controllers inherit from a common base controller.
This allowed my to use IoC on the base controller and not just reach out into global shared for such data.
I'm wondering if there is an alternate way of inserting this kind of code into the MVC pipeline?
The best way is using the ActionFilterAttribute. I'll show you how to use it in .Net Core and .Net Framework.
.Net Core 2.1 & 3.1
public class ViewBagActionFilter : ActionFilterAttribute
{
public ViewBagActionFilter(IOptions<Settings> settings){
//DI will inject what you need here
}
public override void OnResultExecuting(ResultExecutingContext context)
{
// for razor pages
if (context.Controller is PageModel)
{
var controller = context.Controller as PageModel;
controller.ViewData.Add("Avatar", $"~/avatar/empty.png");
// or
controller.ViewBag.Avatar = $"~/avatar/empty.png";
//also you have access to the httpcontext & route in controller.HttpContext & controller.RouteData
}
// for Razor Views
if (context.Controller is Controller)
{
var controller = context.Controller as Controller;
controller.ViewData.Add("Avatar", $"~/avatar/empty.png");
// or
controller.ViewBag.Avatar = $"~/avatar/empty.png";
//also you have access to the httpcontext & route in controller.HttpContext & controller.RouteData
}
base.OnResultExecuting(context);
}
}
Then you need to register this in your startup.cs.
.Net Core 3.1
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options => {
options.Filters.Add<Components.ViewBagActionFilter>();
});
}
.Net Core 2.1
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add<Configs.ViewBagActionFilter>();
});
}
Then you can use it in all views and pages
#ViewData["Avatar"]
#ViewBag.Avatar
.Net Framework (ASP.NET MVC .Net Framework)
public class UserProfilePictureActionFilter : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.Controller.ViewBag.IsAuthenticated = MembershipService.IsAuthenticated;
filterContext.Controller.ViewBag.IsAdmin = MembershipService.IsAdmin;
var userProfile = MembershipService.GetCurrentUserProfile();
if (userProfile != null)
{
filterContext.Controller.ViewBag.Avatar = userProfile.Picture;
}
}
}
register your custom class in the global. asax (Application_Start)
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalFilters.Filters.Add(new UserProfilePictureActionFilter(), 0);
}
Then you can use it in all views
#ViewBag.IsAdmin
#ViewBag.IsAuthenticated
#ViewBag.Avatar
Also there is another way
Creating an extension method on HtmlHelper
[Extension()]
public string MyTest(System.Web.Mvc.HtmlHelper htmlHelper)
{
return "This is a test";
}
Then you can use it in all views
#Html.MyTest()
Since ViewBag properties are, by definition, tied to the view presentation and any light view logic that may be necessary, I'd create a base WebViewPage and set the properties on page initialization. It's very similar to the concept of a base controller for repeated logic and common functionality, but for your views:
public abstract class ApplicationViewPage<T> : WebViewPage<T>
{
protected override void InitializePage()
{
SetViewBagDefaultProperties();
base.InitializePage();
}
private void SetViewBagDefaultProperties()
{
ViewBag.GlobalProperty = "MyValue";
}
}
And then in \Views\Web.config, set the pageBaseType property:
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="MyNamespace.ApplicationViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
</system.web.webPages.razor>
Un-tried by me, but you might look at registering your views and then setting the view data during the activation process.
Because views are registered on-the-fly, the registration syntax doesn't help you with connecting to the Activated event, so you'd need to set it up in a Module:
class SetViewBagItemsModule : Module
{
protected override void AttachToComponentRegistration(
IComponentRegistration registration,
IComponentRegistry registry)
{
if (typeof(WebViewPage).IsAssignableFrom(registration.Activator.LimitType))
{
registration.Activated += (s, e) => {
((WebViewPage)e.Instance).ViewBag.Global = "global";
};
}
}
}
This might be one of those "only tool's a hammer"-type suggestions from me; there may be simpler MVC-enabled ways to get at it.
Edit: Alternate, less code approach - just attach to the Controller
public class SetViewBagItemsModule: Module
{
protected override void AttachToComponentRegistration(IComponentRegistry cr,
IComponentRegistration reg)
{
Type limitType = reg.Activator.LimitType;
if (typeof(Controller).IsAssignableFrom(limitType))
{
registration.Activated += (s, e) =>
{
dynamic viewBag = ((Controller)e.Instance).ViewBag;
viewBag.Config = e.Context.Resolve<Config>();
viewBag.Identity = e.Context.Resolve<IIdentity>();
};
}
}
}
Edit 2: Another approach that works directly from the controller registration code:
builder.RegisterControllers(asm)
.OnActivated(e => {
dynamic viewBag = ((Controller)e.Instance).ViewBag;
viewBag.Config = e.Context.Resolve<Config>();
viewBag.Identity = e.Context.Resolve<IIdentity>();
});
Brandon's post is right on the money. As a matter of fact, I would take this a step further and say that you should just add your common objects as properties of the base WebViewPage so you don't have to cast items from the ViewBag in every single View. I do my CurrentUser setup this way.
You could use a custom ActionResult:
public class GlobalView : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
context.Controller.ViewData["Global"] = "global";
}
}
Or even a ActionFilter:
public class GlobalView : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = new ViewResult() {ViewData = new ViewDataDictionary()};
base.OnActionExecuting(filterContext);
}
}
Had an MVC 2 project open but both techniques still apply with minor changes.
You don't have to mess with actions or change the model, just use a base controller and cast the existing controller from the layout viewcontext.
Create a base controller with the desired common data (title/page/location etc) and action initialization...
public abstract class _BaseController:Controller {
public Int32 MyCommonValue { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
MyCommonValue = 12345;
base.OnActionExecuting(filterContext);
}
}
Make sure every controller uses the base controller...
public class UserController:_BaseController {...
Cast the existing base controller from the view context in your _Layout.cshml page...
#{
var myController = (_BaseController)ViewContext.Controller;
}
Now you can refer to values in your base controller from your layout page.
#myController.MyCommonValue
If you want compile time checking and intellisense for the properties in your views then the ViewBag isn't the way to go.
Consider a BaseViewModel class and have your other view models inherit from this class, eg:
Base ViewModel
public class BaseViewModel
{
public bool IsAdmin { get; set; }
public BaseViewModel(IUserService userService)
{
IsAdmin = userService.IsAdmin;
}
}
View specific ViewModel
public class WidgetViewModel : BaseViewModel
{
public string WidgetName { get; set;}
}
Now view code can access the property directly in the view
<p>Is Admin: #Model.IsAdmin</p>
I have found the following approach to be the most efficient and gives excellent control utilizing the _ViewStart.chtml file and conditional statements when necessary:
_ViewStart:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
var CurrentView = ViewContext.Controller.ValueProvider.GetValue("controller").RawValue.ToString();
if (CurrentView == "ViewA" || CurrentView == "ViewB" || CurrentView == "ViewC")
{
PageData["Profile"] = db.GetUserAccessProfile();
}
}
ViewA:
#{
var UserProfile= PageData["Profile"] as List<string>;
}
Note:
PageData will work perfectly in Views; however, in the case of a
PartialView, it will need to be passed from the View to
the child Partial.
I implemented the ActionFilterAttribute solution from #Mohammad Karimi. It worked well as I had the same scenario as the OP. I needed to add data to every view. The action filter attribute was executed for every Razor page request, but it was also called for every web API controller request.
Razor Pages offers a page filter attribute to avoid unnecessary execution of the action filter when a web API controller request is made.
Razor Page filters IPageFilter and IAsyncPageFilter allow Razor Pages to run code before and after a Razor Page handler is run.
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
namespace MyProject
{
// learn.microsoft.com/en-us/aspnet/core/razor-pages/filter?view=aspnetcore-6.0
// "The following code implements the synchronous IPageFilter"
// Enable the page filter using 'services.AddRazorPages().AddMvcOptions( ... )
// in the 'ConfigureServices()' startup method.
public class ViewDataPageFilter : IPageFilter
{
private readonly IConfiguration _config;
public ViewDataPageFilter(IConfiguration config)
{
_config = config;
}
// "Called after a handler method has been selected,
// but before model binding occurs."
public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
}
// "Called before the handler method executes,
// after model binding is complete."
public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
PageModel page = context.HandlerInstance as PageModel;
if (page == null) { return; }
page.ViewData["cdn"] = _config["cdn:url"];
}
// "Called after the handler method executes,
// before the action result."
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
}
}
}
As per the sample in the filter methods for Razor Pages documentation, the page filter is enabled by:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add(new ViewDataPageFilter(Configuration));
});
}
I am trying to use Unity 2.0 for my current project with MVC and having trouble configuring paramter injection in the web.config file.
Here's what I have:
1) A Home Controller:
public class HomeController : Controller
{
IRepository repository = null;
public HomeController()
{
// Always calls this constructor. Why?
// Should be calling the constructor below that takes IRepository.
}
public HomeController(IRepository repository)
{
// Should be calling this constructor!!!
this.repository = repository;
}
public ActionResult Index()
{
List<int> intList = this.repository.GetInts();
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
A basic controller with two constructors. The 1st one takes no arguments, and the 2nd one takes IRepository as an argument (that's supposed to be injected by Unity)
2) SQL Repository
public class SQLRepository : IRepository
{
private string connectionString = null;
public SQLRepository(string connectionString)
{
this.connectionString = connectionString;
}
#region IRepository Members
public List<int> GetInts()
{
return new List<int>() { 1, 2, 3, 4, 5 };
}
#endregion
}
A future to be SQL repository, but for now it just implements 1 member of the IRepository interface, namely GetInts() and returns a list of integers.
3) IRepository Interace
public interface IRepository
{
List<int> GetInts();
}
An interface.
4) Application_Start() Event in my Global.asax file.
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
IUnityContainer container = new UnityContainer();
UnityConfigurationSection section = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
section.Configure(container, "Default");
}
This is used to read the Unity 2.0 configuration from the web.config file in order to register and map types etc.
6) The Unity 2.0 Configuration Section in web.config
<unity>
<typeAliases>
<typeAlias alias="string" type="System.String, mscorlib" />
<typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity" />
<typeAlias alias="IRepository" type="NewMVCApp.Interfaces.IRepository, NewMVCApp" />
<typeAlias alias="SQLRepository" type="NewMVCApp.Repository.SQLRepository, NewMVCApp" />
</typeAliases>
<containers>
<container name="Default">
<types>
<type type="IRepository" mapTo="SQLRepository">
<lifetime type="singleton" />
<constructor>
<param name="connectionString">
<value value="ApplicationServices" />
</param>
</constructor>
</type>
</types>
</container>
</containers>
This is Unity 2.0 configuration section that I use. As you can see It typeAlias for both my IRepository and my SQLRepository classes and then maps IRepository to SQLRepository. So that anytime IRepository is requested, SQLRepository instance will be supplied. Also, I want to pass a connection string via the constructor to my SQLRepository.
5) So, what am I trying to do?
I am trying to use Unity 2.0 to pass in the instance of IRepository (SQLRepository) to my HomeController. But for some reason the default, parameterless constructor, for the HomeController() gets invoked. But HomeController(IRepository repository) never gets called. I am pretty sure that I did not set things up properly in the web.config file. But I am not sure how do set things up properly so the correct constructor on the HomeController gets called. Please help:)
&Thank you:)
Your Unity configuration looks fine. The problem is that you haven't hooked up Unity to the MVC framework, so the framework isn't using the container to create your controller, instead MVC is using the default logic, which calls the default constructor.
You need two things. First, an implementation of IControllerFactory. I usually use this one:
public class UnityControllerFactory : DefaultControllerFactory
{
private readonly IUnityContainer container;
public UnityControllerFactory(IUnityContainer container)
{
this.container = container;
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if(controllerType != null)
return container.Resolve(controllerType) as IController;
return base.GetControllerInstance(requestContext, controllerType);
}
}
Second, you need to tell the MVC framework to use this controller factory instead of it's default one. You do this in your Application_Start handler. Do this:
ControllerBuilder.Current.SetControllerFactory(
new UnityControllerFactory(container));
Once you've done that, your controllers will be created through the container and everything should start working.
Thank you so much Chris!! That was it! Here's the how the Application_Start() event should look like in MVC2 using Unity 2.0:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
IUnityContainer container = new UnityContainer();
IControllerFactory controllerFactory = new UnityControllerFactory(container);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
// Do the line below only if you want to Register IoC programatically
//container.RegisterType<IRepository, SQLRepository>(new ContainerControlledLifetimeManager());
UnityConfigurationSection section = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
section.Configure(container, "Default");
}
Try with Unity.Mvc nuget packages. You can use container.LoadConfiguration(); in UnityConfig class.
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
container.LoadConfiguration();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
}
Then you can update web.config as below.
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
</configSections>
<unity configSource="unity.config" />
Complete article can be found # MVC 5 with Unity for Dependency Injection.