I am using MVC 2.
I have a BaseController class that every Controller uses. In this base controller class there is a property called IsAdministrator. I need to use this method in my view's HTML part. How would I do this?
EDIT:
My property in my BaseController is defined like this:
public bool IsAdministratorUser
{
get { return ... }
}
One way would be to use an HTML helper:
public static class HtmlExtensions
{
public static bool IsAdministrator(this HtmlHelper htmlHelper)
{
var controller = htmlHelper.ViewContext.Controller as BaseController;
if (controller == null)
{
throw new Exception("The controller used to render this view doesn't inherit from BaseContller");
}
return controller.IsAdministrator;
}
}
And in your view:
<% if (Html.IsAdministrator()) { %>
<% } %>
UPDATE:
#jfar's comment about the MVC paradigm is correct. Here's what you could do in practice to implement it. You could define a base view model class that all your view models derive from:
public class BaseViewModel
{
public bool IsAdministrator { get; set; }
}
and then write a custom action filter attribute which will execute after the action and set the property:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AdministratorInjectorAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var result = filterContext.Result as ViewResultBase;
if (result != null)
{
// the action returned a strongly typed view and passed a model
var model = result.ViewData.Model as BaseViewModel;
if (model != null)
{
// the model derived from BaseViewModel
var controller = filterContext.Controller as BaseController;
if (controller != null)
{
// The controller that executed this action derived
// from BaseController and posses the IsAdministrator property
// which is used to set the view model property
model.IsAdministrator = controller.IsAdministrator;
}
}
}
}
}
And the last part is to decorate the BaseController with this attribute:
[AdministratorInjector]
public abstract class BaseController : Controller
{
public bool IsAdministrator { get; set; }
}
Finally if your view is strongly typed to a model that derives from BaseViewModel you could directly use the IsAdministrator property:
<% if (Model.IsAdministrator) { %>
<% } %>
Probably a bit more code than the HTML helper, but your consciousness about respecting MVC paradigm will be clear.
Related
I am developing a MVC application.
I want to send a controller for validating purpose to Validation class.
That class will validate the controllers properties and send the result.
I am not gettting, how to get name and properties of the controller after
getting it in class.
Below code is the Controller class code and I send this controller to class named validation class.
[HttpPost]
public ActionResult Create(Location location)
{
if (ModelState.IsValid)
{
Validations v = new Validations();
Boolean ValidProperties = true;
//Sends the controller to Validation class
v.ValidProperty(this);
if (ValidProperties == true)
{
db.Locations.Add(location);
db.SaveChanges();
return RedirectToAction("Index");
}
}
}
And Below code is the class named Validations where I want to validate the controller .
Now I am not getting how to get the name of controller and its properties.
public class Validations
{
string PropertName;
public void ValidProperty(Controller ctr)
{
var name1 = ctr;
string s = ctr. ????????
//How to get Controller Name and its properties ?
}
}
use reflection to get name as:
var name = this.GetType().Name;
Or you can create a custom base controller of your choice, add properties, methods to it and deal with derived controllers as:
public abstract class BaseController : Controller
{
// add other properties as needed
public abstract string Name { get; protected set; }
public virtual void ValidProperty()
{
string s = Name;
//something esle
}
}
public class YourController : BaseController
{
private string _name;
public override string Name
{
get { return _name; }
protected set { _name = "Your_Name"; }
}
[HttpPost]
public ActionResult Create(Location location)
{
if (ModelState.IsValid)
{
bool validProperties = true;
// Deals with a base controller method
ValidProperty();
// or something like this, if you prefer
var controller = (BaseController) this;
Validations v = new Validations();
//Sends the controller to Validation class
v.ValidProperty(controller);
if (validProperties)
{
db.Locations.Add(location);
db.SaveChanges();
return RedirectToAction("Index");
}
}
return Content(string.Empty);
}
}
I know it's a bad practice to use database in the view. However, I'm also passing the User object and I wonder how I can make it easy to use.
I love the way it works in Ruby On Rails. You just create an #instance_variable in before_filter and call it from the controllers and from the views.
You can't do this in ASP.NET MVC though. So I created a class with all the data I need to pass to the view (DataContext and User):
public class XData
{
public DBDataContext DB { get; set; }
public User User { get; set; }
}
In controller's Initialize method I get all the data:
public XData X;
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
X = new XData();
X.DB = ...;
X.User = ....;
}
Works great: I can get the database object from the view like this:
<%= Model.X.DB.Users.First().Name %>
In order to pass the data to the view, I have to do the following:
public ActionResult Foo()
{
return View(new FooModel
{
X = X,
HelloMessage = "Hello world!"
});
}
The thing I don't like here is that I always have to write the X = X thing. How can I initialize that automatically?
Thanks
I've seen a similar problem where a lot of controllers return a similar model with only a few customizations. In this case, create an abstract base model that other models derive from, and a function that returns the particular model you need with the base X = X and so forth already set.
For example, consider this:
public abstract class MyBaseModel
{
public User User { get; set; }
}
public class FooModel : MyBaseModel
{
public string FooMessage { get; set; }
}
public class BarModel : MyBaseModel
{
public string BarMessage { get; set; }
}
public class MyController : Controller
{
public ActionResult Foo()
{
var model = this.GetModel<FooModel>();
// Set the properties on FooModel.
model.FooMessage = "Hello world!"
return View(model);
}
public ActionResult Bar()
{
var model = this.GetModel<BarModel>();
// Set the properties on BarModel.
model.BarMessage = "Hello world 2!"
return View(model);
}
protected T GetModel<T>() where T : MyBaseModel, new()
{
T model = new T();
// Set the properties on MyBaseModel.
model.User = ...;
return model;
}
}
If you want to share MyBaseModel and GetModel among many controllers, extract it out to a ModelProvider or something similar that is supplied to each controller, ideally through dependency injection.
You could place X in ViewData and write an HtmlHelper extension method to access X or override the View method of the controller and add a little reflection logic that maps every instance property of the controller to properties with matching names of your model (I guess Automapper could help here...)
I think you'll need to start by making your view strongly typed:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Namespace.XData>" %>
and in your controller:
public ActionResult Foo()
{
var X = new XData();
X.User = ....;
X.SomeProperty = ...;
X.Message = "Hello world!";
return View(X);
}
which allows you to access the object in your view like so:
<%: Model.User.UserName %>
<%: Model.Message %>
I try to inhering from ViewPage as shown in this question Inheriting from ViewPage
But I get a
Compiler Error Message: CS1061: 'object' does not contain a definition for 'Spot' and no extension method 'Spot' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
My viewpage, normally I can do Model.ChildProperty(Spot) when I inherit from ViewPage directly, so I do that here too. But it fails.
<%# Page Language="C#" Inherits="Company.Site.ViewPageBase<WebSite.Models.SpotEntity>" %>
<h1><%= Html.Encode(Model.Spot.Title) %></h1>
To get it working correctly I have to do like this:
<%# Page Language="C#" Inherits="Company.Site.ViewPageBase<WebSite.Models.SpotEntity>" %>
<h1><%= Html.Encode(((WebSite.Models.SpotEntity)Model).Spot.Title) %></h1>
Here is my classes:
namespace Company.Site
{
public class ViewPageBase<TModel> : Company.Site.ViewPageBase where TModel:class
{
private ViewDataDictionary<TModel> _viewData;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public new ViewDataDictionary<TModel> ViewData
{
get
{
if (_viewData == null)
{
SetViewData(new ViewDataDictionary<TModel>());
}
return _viewData;
}
set
{
SetViewData(value);
}
}
protected override void SetViewData(ViewDataDictionary viewData)
{
_viewData = new ViewDataDictionary<TModel>(viewData);
base.SetViewData(_viewData);
}
}
public class ViewPageBase : System.Web.Mvc.ViewPage
{
}
}
So how do I get it to work without the explicit cast?
Is there a reason you need ViewPageBase to derive from ViewPageBase? It doesn't look like ViewPageBase adds anything.
The simplest solution is to change ViewPageBase to derive from ViewPage, not from ViewPageBase. The Model property of ViewPageBase is of type object. The Model property of ViewPage is TModel (in other words, the type you specify).
If you absolutely must derive from ViewPageBase, you can try the following (this is the pattern that ViewPage uses:
public class ViewPageBase<TModel> : ViewPageBase {
private ViewDataDictionary<TModel> _viewData;
public new TModel Model {
get {
return ViewData.Model;
}
}
public new ViewDataDictionary<TModel> ViewData {
get {
if (_viewData == null) {
SetViewData(new ViewDataDictionary<TModel>());
}
return _viewData;
}
set {
SetViewData(value);
}
}
protected override void SetViewData(ViewDataDictionary viewData) {
_viewData = new ViewDataDictionary<TModel>(viewData);
base.SetViewData(_viewData);
}
}
In asp.net MVC, dependency injection with controllers is simple and straightforward. Now, I'd like to remove most of the logic from views by using helpers. The problem is that these helpers use some of the objects that are injected.
Let me write an example:
public interface ISessionData
{
List<string> IdList {get;}
}
public MyController : Controller
{
public MyController(ISessionData sessionData)
{
...
}
}
session data is injected into controller. So far so good. But now I have a helper. Let's say it looks like this:
public class MyHelper
{
private readonly ISessionData sessionData;
public MyHelper(ISessionData sessionData)
{
this.sessionData = sessionData;
}
public bool CheckSomethingExistsInSession(string id)
{
return sessionData.IdList.Any(x => x.Id.Equals(id));
}
}
Now what? I'd like MyHelper to be injected into view. Only way I can see is adding this helper to model and passing it to view every time. Any other ideas?
In MVC it is better to pass ISessionData data from Controller to View (using ViewModel or ViewData):
ViewData["Session"] = sessionData.IdList.ToList();
And remove ISessionData dependency from the helper. Something like this:
public class MyHelper
{
//private readonly ISessionData sessionData;
public MyHelper(/*ISessionData sessionData*/)
{
//this.sessionData = sessionData;
}
public bool CheckSomethingExistsInSession(string id, IList<...> sessionData)
{
return sessionData.Any(x => x.Id.Equals(id));
}
}
In View:
<% var somethingExists = new MyHelper().CheckSomethingExistsInSession(
1, ViewData["Session"] as IList<...>); %>
UPDATED:
public static class MyHelper
{
public static bool CheckSomethingExistsInSession(string id, IList<...> sessionData)
{
return sessionData.Any(x => x.Id.Equals(id));
}
}
<% var somethingExists = MyHelper.CheckSomethingExistsInSession(
1, ViewData["Session"] as IList<...>); %>
You should remove session logic from your controller's constructor and insert it into the controllers action method by using an IModelBinder. See below:
public class SessionDataModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Get/create session data implementating ISeesionData or whatever here. This will be return to the controller action method.
return new SessionData()
}
}
On you controller you would do something like:
public MyController : Controller
{
public MyController()
{
....
}
public ActionResult Index(ISessionData sessionData)
{
// do stuff with ISessionData.
// Redirect or whatever.
return this.RedirectToAction("Index");
}
}
You need to add your IModelBinder like below for it to be called. You can do this in the http application's startup.
System.Web.Mvc.ModelBinders.Binders[typeof(ISessionData)] = new SessionDataModelBinder();
Is it possible to inherit from both ViewPage and ViewPage<T>?? Or do I have to implement both. Currently this is what I have for ViewPage. Do i need to repeat myself and do the same for ViewPage<T>??
public class BaseViewPage : ViewPage
{
public bool LoggedIn
{
get
{
if (ViewContext.Controller is BaseController)
return ((BaseController)ViewContext.Controller).LoggedOn;
else
return false;
}
}
}
Create both versions:
public class BaseViewPage : ViewPage
{
// put your custom code here
}
public class BaseViewPage<TModel> : BaseViewPage where TModel : class
{
// code borrowed from MVC source
private ViewDataDictionary<TModel> _viewData;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public new ViewDataDictionary<TModel> ViewData {
get {
if (_viewData == null) {
SetViewData(new ViewDataDictionary<TModel>());
}
return _viewData;
}
set {
SetViewData(value);
}
}
protected override void SetViewData(ViewDataDictionary viewData) {
_viewData = new ViewDataDictionary<TModel>(viewData);
base.SetViewData(_viewData);
}
}
then
public class MyCustomView : BaseViewPage
{
}
or
public class MyCustomView : BaseViewPage<MyCustomViewData>
{
}
Depending on how you are doing things you might want to look at
ViewContext.HttpContext.Request.IsAuthenticated
it might save you some time instead of extending the ViewPage class.
If there is some other data that you are after you could maybe write an extension method to one of the classes that provides the data. E.g. if LoggedIn was stored in the session you could extend the context to give you an IsLoggedIn() in method.
Edit:
As your extending a class that is already available in the both the base and strongly typed view it will be available in both. The only other way around is to reimplement the strongly typed version as above.
I wouldn't put this in the View, instead I'd have it as a property on the ViewModel (have a BaseViewModel). It will be easier to test as well as ensuring you're not going down the slope of putting business logic into the views.