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
Related
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
My theme has some sort of breadcrumb. The controller is always the category. To avoid repeat myself, I want to set it in the constructor of the controller for all actions like this:
class MyController:Controller{
public MyController() {
ViewBag.BreadcrumbCategory = "MyCategory";
}
}
When I access ViewBag.BreadcrumbCategory in the layout-view, its null. In a Action it works:
class MyController:Controller{
public IActionResult DoSomething() {
ViewBag.BreadcrumbCategory = "MyCategory";
}
}
I'm wondering that setting a ViewBag property is not possible in a constructor? It would be annoying and no good practice to have a function called on every action which do this work. In another question using the constructor was an accepted answear, but as I said this doesn't work, at least for ASP.NET Core.
There is an GitHub issue about it and it's stated that this is by design. The answer you linked is about ASP.NET MVC3, the old legacy ASP.NET stack.
ASP.NET Core is written from scratch and uses different concepts, designed for both portability (multiple platforms) as well as for performance and modern practices like built-in support for Dependency Injection.
The last one makes it impossible to set ViewBag in the constructor, because certain properties of the Constructor base class must be injected via Property Injection as you may have noticed that you don't have to pass these dependencies in your derived controllers.
This means, when the Controller's constructor is called, the properties for HttpContext, ControllerContext etc. are not set. They are only set after the constructor is called and there is a valid instance/reference to this object.
And as pointed in the GitHub issues, it won't be fixed because this is by design.
As you can see here, ViewBag has a dependency on ViewData and ViewData is populated after the controller is initialized. If you call ViewBag.Something = "something", then you it will create a new instance of the DynamicViewData class, which will be replaced by the one after the constructor gets initialized.
As #SLaks pointed out, you can use an action filter which you configure per controller.
The following example assumes that you always derive your controllers from Controller base class.
public class BreadCrumbAttribute : IActionFilter
{
private readonly string _name;
public BreadCrumbAttribute(string name)
{
_name = name;
}
public void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
var controller = context.Controller as Controller;
if (controller != null)
{
controller.ViewBag.BreadcrumbCategory = _name;
}
}
}
Now you should be able to decorate your controller with it.
[BreadCrumb("MyCategory")]
class MyController:Controller
{
}
I have the same issue and solve it overriding the OnActionExecuted method of the controller:
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
ViewBag.Module = "Production";
}
Here is a better way to do this for .NET Core 3.x, use the ResultFilterAttribute:
Create your own custom filter attribute that inherits from ResultFilterAttribute as shown below:
public class PopulateViewBagAttribute : ResultFilterAttribute
{
public PopulateViewBagAttribute()
{
}
public override void OnResultExecuting(ResultExecutingContext context)
{
// context.HttpContext.Response.Headers.Add(_name, new string[] { _value });
(context.Controller as MyController).SetViewBagItems();
base.OnResultExecuting(context);
}
}
You'll need to implement the method SetViewBagItems to populate your ViewBag
public void SetViewBagItems()
{
ViewBag.Orders = Orders;
}
Then Decorate your Controller class with the new attribute:
[PopulateViewBag]
public class ShippingManifestController : Controller
That's all there is to it! If you are populating ViewBags all over the place from your constructor, then you may consider creating a controller base class with the abstract method SetViewBagItems. Then you only need one ResultFilterAttribute class to do all the work.
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.
Simple scenario
public interface IFoo
{
int GetData();
}
public class Foo : IFoo
{
[CacheResult]
public int GetData() { .... }
}
public class MyController
{
[Dependency]
IFoo Foo {get; set;}
}
If I register the interface manually, resolving MyController works fine:
container.RegisterType<IFoo, Foo>(new ContainerControlledLifetimeManager(),
new InterceptionBehavior<PolicyInjectionBehavior>(),
new Interceptor<InterfaceInterceptor>());
var controller = container.Resolve<MyController>();
If I try to use auto-registration:
container.RegisterTypes(
AllClasses.FromLoadedAssemblies(),
WithMappings.FromMatchingInterface,
WithName.Default,
WithLifetime.ContainerControlled,
getInjectionMembers: t => new InjectionMember[]
{
new Interceptor<InterfaceInterceptor>(),
new InterceptionBehavior<PolicyInjectionBehavior>(),
});
var controller = container.Resolve<MyController>();
The resolve fails with a ResolutionFailedException because the Type passed must be an interface. Of course, if I make it an interface, it will work, but only if it is named Controller. If I call it, MyController or SqlController or whatever, then the mapping fails because it cannot resolve the interface.
I was hoping to just do an assembly scan, similar to what the Spring framework does, but I have not been able to figure it out.
What am I missing? Or is this not possible in Unity?
The problem is that AllClasses.FromLoadedAssemblies is matching and registering your controller as well. Then when Unity tries to resolve the controller (not IFoo), it finds that the controller is not registered with an interface.
Here's a helper that will reduce your registrations to only those classes that have a matching interface.
public static class TypeFilters
{
public static IEnumerable<Type> WithMatchingInterface(this IEnumerable<Type> types)
{
return types.Where(type =>
type.GetTypeInfo().GetInterface("I" + type.Name) != null);
}
}
and then you can use this to modify your registration like so...
AllClasses.FromLoadedAssemblies().WithMatchingInterface()
I have an MVC 4 project and am trying to inject a PerWebRequest object into my controller. However it appears the object is not being recreated across multiple requests
private static IWindsorContainer InitializeWindsor()
{
var container = new WindsorContainer().Install(FromAssembly.This());
// Add Factory facility
container.AddFacility<TypedFactoryFacility>();
// Register all controllers from this assembly
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
container.Register(
AllTypes.FromAssembly(assembly).BasedOn<Controller>().Configure(c => c.LifestyleTransient())
);
}
// Register HTTP Handlers
container.Register(Component.For<HttpRequestBase>().LifeStyle.PerWebRequest.UsingFactoryMethod(() => new HttpRequestWrapper(HttpContext.Current.Request)));
container.Register(Component.For<HttpContextBase>().LifeStyle.PerWebRequest.UsingFactoryMethod(() => new HttpContextWrapper(HttpContext.Current)));
// Register components
container.Register(Component.For<PerWebRequestObject>().LifeStyle.PerWebRequest);
}
Here's my PerWebRequestObject:
public class PerWebRequestObject
{
public DateTime DateCreated { get; set; }
public PerWebRequestObject()
{
DateCreated = DateTime.UtcNow;
Debug.WriteLine("Created: " + DateCreated.ToLongTimeString());
}
}
Here's my TestController:
public class TestController : BaseController
{
public PerWebRequestObject pwrObject { get; set; }
public ActionResult Test()
{
Debug.WriteLine(pwrObject.DateCreated.ToLongTimeString());
return new ContentResult();
}
}
Why is this not working? Note that I'm using the WindsorControllerFactory and releasing the Controller like so:
public override void ReleaseController(IController controller)
{
var disposableController = controller as IDisposable;
if (disposableController != null)
disposableController.Dispose();
_windsorContainer.Release(controller);
}
Two things:
how do you know the object is not lifecycled properly (not re-created across web-requests)?
One possible explanation is that your dependency looks like this:
controller --> a singleton --> your per-web-request-component
this is a lifestyle mismatch, and that would explain the behaviour you're seing
do not dispose the controller. That's container's job.
Upgrading to Castle Windsor v3.1 fixed the issue for us without needing to change any code.
I assume there must've been an issue with the combination of Windsor v3.0 + MVC 4.