mvc and partials is this not breaking the mvc pattern? - asp.net-mvc

I have a partial view that displays a search options like search by category, region, date etc. This exists on every page. The partial uses a viewmodel containing lists of regions, cats etc.
As this is being used on every page - I have to load these properties on the viewmodel in every action in my controllers to ensure the data is available to the partial view. Not that happy with that. (Have simply used inherited viewmodels)
I see the partial can call a renderaction method on the controller to get the data, but now I would have a view calling data from a controller - breaking the mvc pattern.
what are other people doing in this situation?

You can use custom ActionFilters to inject common functionality to your actions/controllers to avoid repeating the same code.
For example :
public class RequiresSearchOptions : ActionFilterAttribute {
public override void OnResultExecuting(ResultExecutingContext filterContext){
filterContext.Controller.ViewData["SearchOptions"] =
GetSearchOptions();
//Or manipulate the model :
//YourViewModel m =
// (YourViewModel)filterContext.Controller.ViewData.Model;
//m.SearchOptions = GetSearchOptions();
}
}
And then decorate your actions/controllers.
[RequiresSearchOptions]
public ActionResult Index() {
return View();
}
//or
[RequiresSearchOptions]
public class HomeController : Controller {
//Actions
}

For a while I have used partial requests to render reused widgets. In my opinion they are are a more MVC way of rendering widgets over RenderAction as they don't require the View to know what action is being called.
My partial requests render partial views so your existing code can be easily migrated. They can also be output cached in the same way as any asp.net mvc action.
Hope this helps.

Related

How to refactor Umbraco for rendering controllers

I have been running an Umbraco v7 site for about 3 years. Traditionally I have not rendered any pages using controllers, I have however set up some controllers that I POST forms to but that is about it. My existing View pages have logic baked into them and use external helper methods instead of controllers.
I am about to develop a new page and thought this would be my time to test a better design pattern. This page will render data from a controller as well as required form submission via a controller. What is the best route to proceed that will also go smoothly if I decide to do a refactor on my existing View pages?
I am more specifically looking for an answer around Render vs Surface controllers and which one would be better.It is my understanding that my routing would be unchanged if I went with a Render controller, but if I went with surface, I would have to have special routing?
But if I used a render controller, this does not support form submission?
Not sure what else I am missing?
Thanks Again,
Devin
You don't have to configure any special routing - everything is baked right in to Umbraco.
As a rule of thumb Surface Controllers are best used for reusable actions, custom controllers (Route Hijacking) are better for adding custom logic to whole pages (Document Types/Templates) in Umbraco.
Both approaches will enable you to accomplish exactly the same results - the only difference between them is abstraction.
Surface controllers are MVC Child Actions that inherit from Umbraco.Web.Mvc.SurfaceController - this adds helpful Umbraco specific properties and methods.
Surface Controllers are good for creating reusable things like forms or anywhere you need a partial to do anything complicated (i.e. backed by a controller). Have a look at the documentation here.
When you use a custom controller to change how pages are rendered that's called Route Hijacking
To do this you create your own controller than inherits from Umbraco.Web.Mvc.RenderMvcController like this:
public class HomeController : Umbraco.Web.Mvc.RenderMvcController
{
public ActionResult MobileHomePage(RenderModel model)
{
//Do some stuff here, the return the base Index method
return base.Index(model);
}
}
This is a custom controller for the "Home" Document Type. You can of course return a custom model that inherits from RenderModel with your own properties and methods.
Fuller examples and documentation can be found here.
Post Requests
Both options allow you to handle POST requests by adding the [httppost] attribute like so:
Surface Controller:
public class YourSurfaceController: SurfaceController
{
public ActionResult YourAction()
{
// Do stuff
}
[HttpPost]
public ActionResult YourAction()
{
// Do stuff on POST
}
}
Controller for route Hijacking:
public class HomeController : Umbraco.Web.Mvc.RenderMvcController
{
public ActionResult MobileHomePage(RenderModel model)
{
//Do some stuff here, the return the base Index method
return base.Index(model);
}
[HttpPost]
public ActionResult MobileHomePage(RenderModel model)
{
//Do some stuff on POST, the return the base Index method
return base.Index(model);
}
}

How to manage models that are being displayed in a shared layout?

I'm working on a new MVC/Razor website, and we've gotten to the point where we need to put in a real menu structure. Currently, our menus are hardcoded into the global shared layout, we need to populate the menus from code, so that we can determine, after login, which menus will be displayed for a user.
We'll want the menus to appear on pretty much every page, except for login and a few other special cases. So we'll continue to one them to be rendered in the shared layout.
It looks simple enough to create an #Html.DisplayFor template that would render a menu item. The question is, how to get data to it?
The shared layout is used with quite a number of views, handling a number of different models, being loaded from a number of controllers.
Adding a List member to each model, and then populating it in each controller, seems tedious, bothersome, and error prone.
Or we could skip adding the collection to each model, and instead have each controller stick it in the ViewBag. That doesn't seem all that great, either.
The only possibility I've been able to dream up, to keep from having to repeat this for every controller, is to define a common base class, derived from Controller, that all of the controllers that use the shared layout could derive from, in turn, that would stuff the MenuItem collection into the ViewBag.
But I'm wondering if I'm missing something. Is there some preferred way of dealing with this situation?
Shared/base view models is a way to go as you mentioned, but in my opinion its not very "single responsibility". Having to inherit from a base model and add menu items on each page is tedious as you mentioned.
If I was you I would use:
#Html.Action("action", "controller")
https://msdn.microsoft.com/en-us/library/ee703457.aspx
This would call an action method, and then you can bind a specific menu model and also return a specific partial view that would be rendered in the view that called it
#Html.Action("TopMenu", "Controller")
[ChildActionOnly]
public PartialViewResult TopMenu()
{
return PartialView(new MenuModel());
}
Create a TopMenu.cshtml
You can even go as far as passing in values into the controller action to modify the output model/view.
You can call #Html.Action from your layout/shared view.
EDIT
Added [ChildActionOnly] as highlighted in comment as this prevents access to the action unless it was called from a parent action and should be used here
If I understand your question correctly...
I prefer to have a MasterViewModel that every ViewModel for every page inherits from. The MasterViewModel holds things common to every page, like the user's name, navigation rules, etc. The shared layout then uses #model MasterViewModel, which is populated by an ActionFilterAttribute.
public class MasterViewModel
{
public IEnumerable<string> NavItems {get; set;}
}
public class HomeIndexViewModel : MasterViewModel
{
// stuff for the home view
}
I then have a MasterViewModelAttribute:
public class MasterViewModelAttribute : ActionFilterAttribute
{
[Inject]
public IDatabase Database { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
ViewResultBase viewResult = filterContext.Result as ViewResultBase;
// not a view result
if (viewResult == null)
{
return;
}
MasterViewModel model = viewResult.ViewData.Model as MasterViewModel;
// not a MasterViewModel view model
if (model == null)
{
return;
}
model.NavItems = // set your nav items
}
}
Then, every controller derives from a base controller, which implements the attribute:
[MasterViewModel]
public class BaseController : Controller
{
}
And for each controller:
public class HomeController : BaseController
{
public ActionResult Index()
{
return View(new HomeIndexViewModel());
}
}
And finally, the layout:
#model MasterViewModel
// omitted
<div class="nav">
#foreach (var item in Model.NavItems)
{
<a></a>
}
</div>
The thing I like about the pattern is that you still get strong type-checking by using ViewModels (as opposed to the ViewBag), and you don't have to deal with all the extra nav stuff when you want to unit test an action, for example.
This also has an advantage over using partial views and #Html.Action() in that you don't incur a second request to the server for every page load.

Reusable Content Box Data In ASP.NET MVC?

If I create a PartialView for a box that holds a header, image and content what is the best way to store the content without using a database?
Example: TurboTax
I doubt the content for the boxes in the sidebar are stored in a database but to make reusable code it would be beneficial to create the structure in a PartialView and populate the content areas. I can create a PartialView and pass a Model from the parent Controller to the PartialView but then I would be stuck copying and pasting that same content if I wanted to use the same box on another page.
For fixed content you might want to think about using XML+XSLT or even HTML snippets in the file system and simply rendering them. An HtmlHelper method might make more sense for this than a partial view - Html.RenderXml() or Html.Include(). The only real difference between these and partial views is that the view engine isn't invoked since there aren't any substitutions. I do this sort of thing with my privacy policy and terms and conditions. I'd certainly consider keeping these cached.
If these really are templates and you are just substituting content, then I think the partial view works well and I would consider putting the data in a database, again, maybe using caching if I found that performance suffered. You could use this in combination with the former -- say keep your images/xml in the file system and a pointer to them in the database so you know which ones to pick in the partial.
Passing data to partial view that is used in many places can be done in many ways:
Create base model class for all your models. In base class define PartialModel property which will be holding model for partial view (there may be many of them if use have many partial views). Now you can populate the PartialModel property in controller action, but to make code more reusable you can create your own Action Filter which will insert the partial view data just after the action method is executed (but before the model is passed to the view)
public class PartialViewModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
BaseViewModel model;
if (filterContext.Controller.ViewData.Model == null)
{
model = new BaseViewModel();
filterContext.Controller.ViewData.Model = model;
}
else
{
model = filterContext.Controller.ViewData.Model as BaseViewModel;
}
model.PartialModel = new PartialModel(...) // Partial model initialization
base.OnActionExecuted(filterContext);
}
}
Then you can use it like:
[PartialViewModel]
public ActionResult Index()
{
//...
}
Another option: you can create BaseController class for all your controllers and create PartialModel on base controller initialization. Then PartialModel can be stored in ViewData[] dictionary. Because using ViewData dictionary in views is bad, create extension method on HtmlHelper like:
public static PartialModel GetPartialModel(this HtmlHelper helper)
{
return helper.viewContext.ViewData["PartialModel"] as PartialModel
}
So you could obtaint the model this way:
<% Html.RenderPartial("MyPartial", Html.GetPartialModel()); %>

ASP.NET StrongTyped Controller-Action View<TView,TModel>(TModel Data)

I'm using Asp.net MVC 1 and I would really like my controller actions to use StronglyTyped View(data) calls that enforce type checking at compile time and still let me use aspx pages under the default view engine. The ViewPages I call are strongly typed, but errors in the action's call to View(data) can't be caught at compile time because the built in Controller View(data) method isn't strongly typed and doesn't even check to see if the page exists at compile time.
I've implemented a partial solution (code below) using this post but (1) I can't get the generic View function to recognize the Type of strong view pages unless I create a code behind for the strongly typed view, and (2) Intellisense and refactoring don't work properly with this method which makes me doubt the reliability of the method I'm using.
Question:
Is there a better way to get type enforcement when calling Views from actions?
Alternative: Is there an alternative method where my action method can create an instance of a viewpage, set some properties directly and then render out its HTML to the action response?
Code:
Here's the base Class all my Controllers Inherit from to achieve what I have so far:
public class StrongController : Controller
{
protected ActionResult View<TView, TModel>(TModel model)
where TView : ViewPage<TModel>
where TModel : class
{
return View(typeof(TView).Name, model);
}
}
And here's an example Controller in use:
namespace ExampleMVCApp.Controllers
{
public class HomeController : StrongController
{
public ActionResult Index()
{
return View<ExampleMVCApp.Views.Home.Index, ExampleData>(new ExampleData());
}
}
}
ViewPage Code Behind Required for Type Recognition... Aspx header didn't work
namespace ExampleMVCApp.Views.Home
{
public class Issue : System.Web.Mvc.ViewPage<ExampleData>
{
}
}
I think you should give the T4MVC helpers a spin (one of the original announcements here). This would at least enable you to get rid of the code you already have, since these templates generate the code based on the Views you already have and you employ these "fake" method calls to address your views.
For having your calls to View to be strongly typed for the specific model declared by your view, I am not exactly sure if these helpers help you with that (though I suspect they do). However, if they don't you can still hack the T4MVC code to do so yourself or get in touch with the original author, David Ebbo, to suggest the feature for addition.

ASP.NET MVC - Updating Multiple Partials/View

How do you guys update (let's say) two partials in a controller action going to the View? For example, you have a left nav partial for links and on the right is your main content. Your main content has a ViewModel that it binds to. Do you guys have a seperate ViewModel for your nav page? If so, do you then create a bigger ViewModel each time that has the main content view model and the left nav ViewModel?
I would have one ViewModel for the nav page (e.g. CategoryListViewModel) and one for the content page (e.g. ProductListViewModel). Then load the CategoryListViewModel in a base controller:
public abstract class BaseController : Controller
{
var _categoryRepository = new CategoryRepository();
protected BaseController()
{
ViewData["Categories"] = _categoryRepository.GetCategories();
}
}
public class ProductsController : BaseController
{
var _productRepository = new ProductRepository();
public ActionResult Index()
{
return View(_productRepository.GetProducts());
}
}
and then in the MasterPage read the categories from the ViewData, and in the content page read the products from the Model on the ViewPage.
You can break the page into different independent sections, have a look at this
page here. Here you have to use Html.RenderAction() for which you have to
download the ASP.NET MVC Futures assembly from codeplex.
Actually I was eventually eluding to this solution. Thanks guys for your input!

Resources