How can I inherit an ASP.NET MVC controller and change only the view? - asp.net-mvc

I have a controller that's inheriting from a base controller, and I'm wondering how I can utilize all of the logic from the base controller, but return a different view than the base controller uses.
The base controller populates a model object and passes that model object to its view, but I'm not sure how I can access that model object in the child controller so that I can pass it to the child controller's view.

A couple points. You can type your return value as ViewResult if you know that's all you're going to return. Then you can interrogate that value from the overridden implementation. More importantly, according to the MVC v1 source, calling View(object) simply sets the ViewData.Model on the controller, then constructs a ViewResult.
Controller.cs:440
protected internal ViewResult View(object model) {
return View(null /* viewName */, null /* masterName */, model);
}
Controller.cs:456
protected internal virtual ViewResult View(string viewName, string masterName, object model) {
if (model != null) {
ViewData.Model = model;
}
return new ViewResult {
ViewName = viewName,
MasterName = masterName,
ViewData = ViewData,
TempData = TempData
};
}
So all you need to do is call the base method and call View(string).
namespace BaseControllers
{
public class CoolController
{
public virtual ViewResult Get()
{
var awesomeModel = new object();
return View(awesomeModel);
}
}
}
public class CoolController : BaseControllers.CoolController
{
public override ViewResult Get()
{
var ignoredResult = base.Get();
// ViewData.Model now refers to awesomeModel
return View("NotGet");
}
}
Of course you waste CPU cycles constructing the ViewResult that you ignore. So instead you can do this:
public class CoolController : BaseControllers.CoolController
{
public override ViewResult Get()
{
var baseResult = base.Get();
baseResult.ViewName = "NotGet";
return baseResult;
}
}
If your base controller returns ActionResult, you'll have to cast it to ViewResult before changing the ViewName.

Sample from my app:
Base class:
public abstract class BaseTableController<T,TU> : BaseController where TU : IGenericService<T>,IModelWrapperService
{
protected readonly TU _service;
public BaseTableController(TU service)
{
_service = service;
_service.ModelWrapper = new ControllerModelStateWrapper(ModelState);
}
public ActionResult Index()
{
return View(_service.List());
}
Inherited:
public class SeverityController : BaseTableController<Severity, ISeverityService>
{
public SeverityController(ISeverityService service)
: base(service)
{
}
//NO CODE INSIDE
}
SeverityController.Index() leads to Views/Severity/Index.aspx. Just had to prepare view. Severity is one of dictionared in my bug tracking application. Every dictionary has similar logic, so I could share some code.

Based on the feedback given on this thread, I've implemented a solution like the one proposed by Antony Koch.
Instead of using an abstract method, I used a concrete, virtual GetIndex method so that I could put logic in it for the base controller.
public class SalesController : Controller
{
// Index view method and model
public virtual ActionResult GetIndex()
{
return View("Index", IndexModel);
}
protected TestModel IndexModel { get; set; }
public virtual ActionResult Index()
{
ViewData["test"] = "Set in base.";
IndexModel = new TestModel();
IndexModel.Text = "123";
return GetIndex();
}
[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult Index(TestModel data, FormCollection form)
{
TryUpdateModel(data, form.ToValueProvider());
IndexModel = data;
return GetIndex();
}
}
// This class will need to be in a different namespace or named differently than the
// parent controller
public class SalesController : MyApp.Controllers.BaseControllers.SalesController
{
// Index view method and model
public override ActionResult GetIndex()
{
return View("ClientIndex", IndexModel);
}
public override ActionResult Index()
{
return base.Index();
}
[AcceptVerbs(HttpVerbs.Post)]
public override ActionResult Index(TestModel data, FormCollection form)
{
return base.Index(data, form);
}
}

public class BaseController : Controller {
protected BaseController() {}
public ActionResult Index()
{
return GetIndex();
}
public abstract ActionResult GetIndex(); }
public class MyController : BaseController {
public MyController() {}
public override GetIndex()
{
return RedirectToAction("Cakes","Pies");
}
}
Just use abstraction to call the bits you need from the sub-classes.

I ended up just putting an extra parameter on the base Controller -- viewName.
Seems to work just fine.
Am I missing any major downsides?
public class SalesController : Controller
{
public virtual ActionResult Index(string viewName)
{
ViewData["test"] = "Set in base.";
TestModel model = new TestModel();
model.Text = "123";
return String.IsNullOrEmpty(viewName) ? View(model) : View(viewName, model);
}
[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult Index(TestModel data, FormCollection form, string viewName)
{
TryUpdateModel(data, form.ToValueProvider());
return String.IsNullOrEmpty(viewName) ? View(data) : View(viewName, data);
}
}
public class SalesController : MyApp.Controllers.BaseControllers.SalesController
{
public override ActionResult Index(string viewName)
{
return base.Index("ClientIndex");
}
[AcceptVerbs(HttpVerbs.Post)]
public override ActionResult Index(TestModel data, FormCollection form, string viewName)
{
return base.Index(data, form, "ClientIndex");
}
}

Related

How do I pass variables to a custom ActionFilter in ASP.NET MVC app

I have a controller in my MVC app for which I'm trying to log details using a custom ActionFilterAttribute, by using the onResultExecuted method.
I read this tutorial to understand and write my own action filter. The question is how do I pass variables from the controller to the action filter?
I want to get the input variables with which a controller is called. Say, the username/user ID.
If (in some situations) an exception is thrown by any controller method, I would want to log the error too.
The controller -
[MyActionFilter]
public class myController : ApiController {
public string Get(string x, int y) { .. }
public string somemethod { .. }
}
The action filter -
public class MyActionFilterAttribute : ActionFilterAttribute {
public override void onActionExecuted(HttpActionExecutedContext actionExecutedContext) {
// HOW DO I ACCESS THE VARIABLES OF THE CONTROLLER HERE
// I NEED TO LOG THE EXCEPTIONS AND THE PARAMETERS PASSED TO THE CONTROLLER METHOD
}
}
I hope I have explained the problem here. Apologies if I'm missing out some basic objects here, I'm totally new to this.
Approach - 1
Action Filter
public class MyActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
}
}
Action Method
[MyActionFilter]
public ActionResult Index()
{
ViewBag.ControllerVariable = "12";
return View();
}
If you pay attention to the screenshot, you can see the ViewBag information
Approach - 2
Action Filter
public class MyActionFilter : ActionFilterAttribute
{
//Your Properties in Action Filter
public string Property1 { get; set; }
public string Property2 { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
}
Action Method
[MyActionFilter(Property1 = "Value1", Property2 = "Value2")]
public ActionResult Index()
{
return View();
}
I suggest another approach, and it is passing parameters to Action Filter as constractor.
[PermissionCheck(Permissions.NewUser)]
public ActionResult NewUser()
{
// some code
}
Then in the ActionFilter:
public class PermissionCheck : ActionFilterAttribute
{
public Permissions Permission { get; set; }
public PermissionCheck(Permissions permission)
{
Permission = permission;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (/*user doesn't have that permission*/)
{
filterContext.Result = new RedirectToRouteResult
(
new RouteValueDictionary
(
new {
controller = "User",
action = "AccessDeny",
error = "You don't have permission to do this action"
}
)
);
base.OnActionExecuting(filterContext);
}
}
}
Which Permissions is an ENUM like:
enum Permissions {NewUser, Edit, Delete, Update, ...}

how to get partialview string in a apicontroller in mvc

i have a apicontroller like this:
public class MenuController : ApiController
{
[HttpGet]
public string GetMenu([FromUri]string page, [FromUri]string menu)
{
}
}
I have a partialview say "menu.cshtml" i want to use that partialview and give the menu in string .
I have tried various functions that says renderpartialviewtostring but they use controller in it but i am using ApiController
Please help
You can derive your own type from IHttpActionResult and do it.
This article speaks about it - http://www.strathweb.com/2013/06/ihttpactionresult-new-way-of-creating-responses-in-asp-net-web-api-2/
You will need a reference to RazorEngine - http://www.nuget.org/packages/RazorEngine/
In your case you can create a StringActionResult derived from IHttpActionResult that does something similar to below.
public class StringActionResult : IHttpActionResult
{
private const string ViewDirectory = #"c:\path-to-views";
private readonly string _view;
private readonly dynamic _model;
public StringActionResult(string viewName, dynamic model)
{
_view = LoadView(viewName);
_model = model;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
var parsedView = RazorEngine.Razor.Parse(_view, _model);
response.Content = new StringContent(parsedView);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
return Task.FromResult(response);
}
private static string LoadView(string name)
{
var view = File.ReadAllText(Path.Combine(ViewDirectory, name + ".cshtml"));
return view;
}
}
and then in your controller, do something like this.
public class MenuController : ApiController
{
[HttpGet]
public StringActionResult GetMenu([FromUri]string page, [FromUri]string menu)
{
return new StringActionResult("menu", new {Page: page, Menu: menu});
}
}

Can I move common code from a method into a base controller with MVC4?

I have the following method in five controllers:
public ActionResult Index(string page, string title) {
var vm = new BaseViewModel();
vm.Role = GetRoleNumber(User);
vm.MenuItems = contentService.GetMenuItems("00", vm.Role);
vm.Menu = pageService.GetMenu(vm.MenuItems, Request.FilePath);
// difference code here for each controller
}
All my controllers inherit from a controller called BaseController.
Is there a way I could move this code into my base controller and call it? If so then what would be the best way to implement this?
This is an exact candidate for the Repository Pattern.
You could create all of these in your Repository class and call that method in each ActionResult method
public void Repository : IRepository
{
public GetMyBaseViewModel()
{
//..implementation here
}
}
public interface IRepository
{
BaseViewModel GetMyBaseViewModel();
}
....
and in your controllers :
...
public class HomeController : Controller
{
//private repository member
private readonly IRepository _repository;
//controller constructors
//injecting the repository here
public HomeController() : this(new Repository())
{
}
public HomeController(IRepository repository)
{
_repository = repository;
}
//methods that call the repository for the vm data context
public ActionResult Index()
{
var vm = _repository.GetMyBaseViewModel();
return View();
}
}
You could make an abstract ActionResult method in your base controller:
protected BaseViewModel vm;
public ActionResult Index(string page, string title) {
vm = new BaseViewModel();
vm.Role = GetRoleNumber(User);
vm.MenuItems = contentService.GetMenuItems("00", vm.Role);
vm.Menu = pageService.GetMenu(vm.MenuItems, Request.FilePath);
try
{
return IndexSupplemental();
}
catch(NotImplementedException ex)
{
// Log and move on; the abstract method is not implemented.
}
return View();
}
protected abstract ActionResult IndexSupplemental();
Then every controller would have to implement this abstract method.
You can move it to a method in your base controller and call it when you need it.
public class BaseController : Controller
{
protected BaseViewModel _viewModel;
public void InitializeViewModel() {
vm = new BaseViewModel();
vm.Role = GetRoleNumber(User);
vm.MenuItems = contentService.GetMenuItems("00", vm.Role);
vm.Menu = pageService.GetMenu(vm.MenuItems, Request.FilePath);
}
}
An example:
public class MyController : BaseController
{
public ActionResult Index(string page, string title)
{
InitializeViewModel();
DoSomething(_viewModel);
}
}
In my projects most of my actions will return a viewmodel that inherits from the BaseViewModel but there are exceptions to this. So what I did was something like this in ControllerBase:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var authData = GetUserData();
if (authData != null)
{
var result = filterContext.Result as ViewResult;
if (result != null)
{
var vm = result.Model as ViewModelBase;
if (vm != null)
{
vm.UserId = authData.UserID;
vm.UserName = User.Identity.Name;
}
}
}
}
What you could do otherwise, as I expect your ViewModel to be of different types, is to create a method similar to this in ControllerBase:
NOTE This does not do what you want. I'm just showing a technique for creating a new instance of a derived class with some initialization code.
protected T Command<T>() where T : BaseCommand, new()
{
var command = new T();
command.IP = Request.UserHostAddress;
if (User != null && User.Identity.IsAuthenticated)
{
var authData = GetUserData();
if (authData != null)
{
command.UserId = authData.UserID;
}
}
return command;
}
Which would be used as
var command = Command<CreateUserCommand>();

Is it possible to have both GET and POST asynchronous controller actions of the same name?

Is it possible to have an AsyncController that has a GET and POST action of the same name?
public class HomeController : AsyncController
{
[HttpGet]
public void IndexAsync()
{
// ...
}
[HttpGet]
public ActionResult IndexCompleted()
{
return View();
}
[HttpPost]
public void IndexAsync(int id)
{
// ...
}
[HttpPost]
public ActionResult IndexCompleted(int id)
{
return View();
}
}
When I tried this I got an error:
Lookup for method 'IndexCompleted' on controller type 'HomeController' failed because of an ambiguity between the following methods:
System.Web.Mvc.ActionResult IndexCompleted() on type Web.Controllers.HomeController
System.Web.Mvc.ActionResult IndexCompleted(System.Int32) on type Web.Controllers.HomeController
Is it possible to have them co-exist in any way or does every asynchronous action method have to be unique?
You can have the multiple IndexAsync methods, but only the one IndexCompleted method eg:
public class HomeController : AsyncController
{
[HttpGet]
public void IndexAsync()
{
AsyncManager.OutstandingOperations.Increment(1);
// ...
AsyncManager.Parameters["id"] = null;
AsyncManager.OutstandingOperations.Decrement();
// ...
}
[HttpPost]
public void IndexAsync(int id)
{
AsyncManager.OutstandingOperations.Increment(1);
// ...
AsyncManager.Parameters["id"] = id;
AsyncManager.OutstandingOperations.Decrement();
// ...
}
public ActionResult IndexCompleted(int? id)
{
return View();
}
}
(Only the attributes on the MethodNameAsync methods are used by MVC, so are not required on the MethodNameCompleted methods)

ASP.NET MVC OutputCacheAttribute: do not cache if a parameter is set?

I have the following action:
public class HomeController : Controller
{
public ActionResult Index(int? id) { /* ... */ }
}
I'd like to [OutputCache] that action, but I'd like that either:
it doesn't use the cache if id == null; or
it uses the cache if id == null but with a different duration.
I think I can achieve this by:
public class HomeController : Controller
{
[OutputCache(VaryByParam = "none", Duration = 3600)]
public ActionResult Index() { /* ... */ }
[OutputCache(VaryByParam = "id", Duration = 60)]
public ActionResult Index(int id) { /* ... */ }
}
However this solution implies 2 actions, when the id is actually optional, so this might create some code repetition. Of course I could do something like
public class HomeController : Controller
{
[OutputCache(VaryByParam = "none", Duration = 3600)]
public ActionResult Index() { return IndexHelper(null); }
[OutputCache(VaryByParam = "id", Duration = 60)]
public ActionResult Index(int id) { return IndexHelper(id); }
private ActionResult IndexHelper(int? id) { /* ... */ }
}
but this seems ugly.
How would you implement this?
I think what you have is probably the cleanest option.
Another option, which I haven't tested, may be to set the VaryByCustom parameter and override GetVaryByCustomString in Global.asax.
public override string GetVaryByCustomString(HttpContext context, string arg)
{
if (arg.ToLower() == “id”)
{
// Extract and return value of id from query string, if present.
}
return base.GetVaryByCustomString(context, arg);
}
See here for more information: http://codebetter.com/blogs/darrell.norton/archive/2004/05/04/12724.aspx

Resources