MVC Layout model design pattern - asp.net-mvc

Every page in my site should have some of the same data, similar to how in SO every page displays info about the current user at the top of the page. The way I implemented this was to have a base controller class that all my controllers derive from. In that base controller's constructor I put my model in the ViewBag, and then my layout page uses that.
I'm running into problems with this because my layouts aren't strongly typed. For example, I have to construct new HtmlHelpers in the layout page:
#{var layoutHtml = new HtmlHelper<LayoutModel>(Html.ViewContext, Html.ViewDataContainer); }
#* OK, now we can use the html helper... *#
#layoutHtml.TextAreaFor(model => model.Feedback)
I really don't want to have to have my models subclass from a layout model, since that would force each action to fill out the shared model data individually, but manually creating HtmlHelpers also seems like a bad idea.
Any thoughts?

I might be wrong, but shouldn't you use partial views for this?
Your solution looks way too complicated.

A base controller is a great way to start. I would also introduce a base viewmodel. The base viewmodel would contain user specific information.
public abstract class BaseController : Controller
{
public string CurrentAccountName
{
get
{
return (HttpContext.User == null) ? null : HttpContext.User.Identity.Name;
}
}
public T CreateViewModel<T>() where T : BaseViewModel, new()
{
T viewModel = new T
{
UserName = CurrentAccountName
};
return viewModel;
}
}
public abstract class BaseViewModel
{
public string UserName { get; set; }
}
Then on each request you would populate your viewmodel with user specific information and whatever information is required for the view. MyViewModel is just a viewmodel that inherits from BaseViewModel.
public class MyController : BaseController
{
public ActionResult Index()
{
MyViewModel viewModel = CreateViewModel<MyViewModel>();
return View(viewModel);
}
}
In the master view I would pass in the BaseViewModel and the View I would pass in the inherited MyViewModel.
Now you have access to your user information in your master view and can pass it to a partial or render it directly to the page.

you can define multiple Layouts which you can use in your appropiate views! Just include them like so:
#{
Layout = "~/Views/Shared/_MySubLayout.cshtml";
}

i believe you can use RenderAction to solve this problem. Because information this action will display is common on all pages, you can put it in BaseController and call it from your site master. it will compute its own model and return that model to partial view which can be strongly typed and you don't have to instantiate htmlHelper the way you are doing now.

Related

Return a View from external Class

I want to choose between multiple clients before returning a view in ASP.NET Core MVC.
So let there be a HomeController with the following code:
public class HomeController : Controller
{
public virtual IActionResult Index()
{
return View();
}
}
Now I have multiple clients, and I want to decide what view will be returned. But not at this place, so I want to write it in another file.
So my question is, is there something possible like this:
public class HomeController : Controller
{
public virtual IActionResult Index()
{
ViewChooser vc = new ViewChooser();
return vc.GetNextView();
}
}
public class ViewChooser
{
public IActionResult GetNextView()
{
// do some stuff and then..
return View("aaaa");
}
}
The class "ViewChooser" does not inherit from Controller, so I can't just write return View().
The reason why I want this to work like this is because I want to choose between multiple workflows without changing the URL. (Otherwise areas would be a possible solution for my problem.)
So if customer A calls www.myserver.com/function1 he get another functionality and view as customer B.
Any ideas? Or am I far away from the solution?
Regards
One option would be to have ViewChooser inherit from Controller. It is, after all, trying to return a view which is something a controller does.
Alternatively, just have ViewChooser return the name of the view:
public class ViewChooser
{
public string GetNextView()
{
// do some stuff and then..
return "aaaa";
}
}
And your controller can use that for its view selection:
public class HomeController : Controller
{
public virtual IActionResult Index()
{
ViewChooser vc = new ViewChooser();
return View(vc.GetNextView());
}
}
This would mean that GetNextView() must always return a valid named view, never another kind of IActionResult. But would decouple the ViewChooser from the MVC framework.
If you have fixed number of clients say "5 clients" then you can create 5 different ActionResult Methods which will return 5 different views. Afterwards you can create a custom attribute where you will write the logic for fetching the client information. You can put this custom attribute over each ActionResult method.

ASP.NET MVC Clean way to inject partial view from action

I have an app with many widgets and their content depends on the user requesting specific route. Simply put: if widget action is requested, its content must be rendered, otherwise it's empty. Consider routes/actions like this:
~/MyApp/Index -> without model; app HTML, without any widgets
~/MyApp/Foo/{id} -> uses FooModel; if ModelState is valid, returns
Index HTML with injected partial view of Foo's widget to div#foo;
otherwise redirects to Index.
~/MyApp/Bar/{id} -> same as Foo, but different model and widget
My foo action :
public ActionResult Foo(string id) {
if (ModelState.IsValid) {
var response = FooService.GetData(id);
// Inject Foo widget to Index
}
return RedirectToAction("Index");
}
I know that it is possible to use ViewBag or other means to send variables and using the condition to decide whether to render partial view or not. But... there should be a better way to do this, right?
I use MVC's Html.RenderActionResult when I want to build shared views with non-trivial binding logic (calling the database, composing complex objects, etc). The binding logic for each widget is contained in a PartialViewResult method, which is called from the *.cshtml file using Html.RenderAction().
ContentController:
public ActionResult Index(int id)
{
var indexViewModel = new IndexViewModel
{
Id = id,
Title = "My Title",
SubHeader = "Wow its 2016"
};
return View(indexViewModel);
}
public PartialViewResult PopularContent(int id)
{
var popularContentViewModel = new List<PopularContentViewModel>();
// query by id to get popular content items
return PartialView("_PopularContent", popularContentViewModel);
}
public PartialViewResult Widget2(int id)
{
return PartialView("_Widget2Partial");
}
Index.cshtml:
#model StackOverflow.RenderAction.ViewModels.IndexViewModel
<h1>#Model.Title</h1>
<h2>#Model.SubHeader</h2>
--RenderAction will call out to the specified route.
--Note the use of the Id parameter from the viewmodel.
#{Html.RenderAction("PopularContent", "Content", new {Model.Id});}
ASP.NET MVC Attribute Routing could a be a nice solution for this:
In your controller:
public class WidgetController : Controller
{
[Route("myapp/foowidget", Name = "FooWidget")]
public ActionResult FooWidget()
{
//create any model and return any view or partial or redirect
}
[Route("myapp/boowidget/{id:int}", Name = "BooWidget")]
public ActionResult BooWidget(int id)
{
//create any model and return any view or partial or redirect
}
}
And then in a View, you can call the Route by name:
#Url.RouteUrl("FooWidget")
or
#Url.RouteUrl("BooWidget")
or
#Html.RenderPartial("FooWidget")
#Url.RouteUrl("BooWidget") will render or concatenate the id that is in current url, if url is /myapp/something/id, because of your Route attribute definition: "myapp/boowidget/{id:int}". In fact #Url.RouteUrl("BooWidget") might extract the id from any current url of the format /controllerName/action/id, though you will have to test for sure.
And notice how you can have a separation of concerns with your WidgetController and your url Routes are not dependent on that controller's name in any way. That is a nice feature of Attribute Routing, you can declare custom routes as well as organize your controllers and break from nameing convention dependency of a controllerName being part of the url controllerName/action a user sees in their browser.
In regards to Html.RenderPartial, I am not sure if RenderPartial "connects" or will be able to route to your RouteName like "FooWidget". If it does great.
If not your solution is this:
public class WidgetController : Controller
{
public ActionResult FooWidget()
{
//model, you choose, return a partial
}
public ActionResult RedirectUser()
{
//do a redirect
}
public ActionResult BooWidget()
{
//any model, any partial
}
public ActionResult BooWidget(int id)
{
//any model, any partial
}
}
Each method in your controller is single purpose, has a distinct signature and does one thing, no conditions to pass in and no decisions required.

How To Use an Attribute (maybe) to point at Layout Page I want

I've got an ASP.NET MVC4 project with standard controllers and views. I have to different master pages I use, depending on a global variable I can reach out and get based on the Request.Url.Host. I've written the code below but it is getting kind of bulky to put in every controller. I've gotten it pretty short but was hoping for a suggestion to make it much cleaner.
private ActionResult IndexBase(string year)
{
var data = null; // real data here for model
var localConfig = LocalConfig.GetLocalValues(Request.Url.Host, null, year);
ViewResult view = localConfig.EventType == "svcc"
? View("Index", "~/Views/Shared/_Layout.cshtml", data)
: View("Index", "~/Views/Shared/_LayoutConf.cshtml", data);
return view;
}
I don't know if this solution works for you, but I would solve it with ViewModel's and a common base controller.
One of the nice things with Layouts is you can pass a base ViewModel with the properties common to all your pages (the users name, for example). In your case, you could store the path to the Layout.
First, the base class every ViewModel derives from:
public class MasterViewModel
{
public string Name { get; set; }
public string Layout { get; set; }
}
I prefer to use a 1:1 mapping of ViewModels to Views. That is, each action gets it's own ViewModel. For example: HomeIndexViewModel for /Home/Index, ProfileEditViewModel for /Profile/Edit, etc.
public class HomeIndexViewModel : MasterViewModel
{
// properties you need for /Home/Index
}
To simplify creating the ViewModels, I add a generic method on a base controller that handles setting all these the common properties:
public class BaseController : Controller
{
protected T CreateViewModel<T>() where T : MasterViewModel, new()
{
User user = db.GetUser(User.Identity.Name);
var localConfig = LocalConfig.GetLocalValues(Request.Url.Host, null, year);
return new T()
{
Name = user.Name,
Layout = localConfig.EventType == "svcc" ? "~/Views/Shared/_Layout.cshtml"
: "~/Views/Shared/_LayoutConf.cshtml"
}
}
}
And finally, just use CreateViewModel() in each of your Actions and things should work:
public class HomeController : BaseController
{
public ActionResult Index()
{
HomeIndexViewModel viewModel = CreateViewModel<HomeIndexViewModel>();
return View(viewModel);
}
}
Inside the Views, you can just set
#model HomeIndexViewModel
#{
Layout = Model.Layout;
}
There's no need to duplicate the path anywhere, and changing the logic on which Layout to show requires you only change it in one place.

Best way to handle Categories Partial View in ASP.NET MVC 3 application

I have a list of blog post categories(~20) in a look up table.
I want to display them on multiple pages as list of hyperlinks that user can click.
I also want to display them in a dropdown list in 2 or more places(different view pages)
The follow works & I see categories as a menu/list of hyperlinks.
But this will cause me modify multiple controller where I need to show the categories.
What is the best practice to handle this so that I have minimal code change?
//#1 I added new class in one of my model:
namespace MyApp.Models
{
...
public class ShowPostModel
{
public Post Post { get; set; }
public IEnumerable<Category> Categories { get; set; }
}
public class Category
{
public string _id { get; set; }
public string Name { get; set; }
}
}
//#2 Populating the controller
namespace MyApp.Controllers
{
public class BlogController : Controller
{
public ActionResult ShowPost()
{
ShowPostModel viewModel = new ShowPostModel();
viewModel.Post = ReadBlogPostFromDB();
viewModel.Categories = ReadCategoriesFromDB();
return View(viewModel);
}
}
}
//#3 This is from my main view for showing the Post:
#Html.Partial("_Categories", Model.Categories)
//#4 This is my _Categories partial view:
#model IEnumerable<MyApp.Models.Category>
<section>
<header><b>Categories</b></header>
<ul style="padding:0;margin:0;">
#foreach (var cat in Model)
{
<li>
#cat.Name
</li>
}
</ul>
</section>
Thanks for reading
Edit:
I made these changes and it seems working as well.
Any comments or improvements I can make here?
//#1 deleted this line from public class ShowPostModel (model is now DRY)
public IEnumerable<Category> Categories { get; set; }//deleted
//#2 created a base controller and inherit from it
public abstract class BlogBaseController : Controller
{
public BlogBaseController()
{
ViewBag.Categories = ReadCategoriesFromDB();
}
}
//#3 force all controller where I need categories to inherit from base controller
public class BlogController : BlogBaseController
//#4 change how I read in my views
#Html.Partial("_Categories", (IEnumerable<MyApp.Models.Category>)#ViewBag.Categories)
If you use the categories in enough places, you can encapsulate this into a base controller class, and override OnActionExecuted.
I would then put the Categories into a property on the ViewBag and pass it into your partial view from there, and leave your view's model alone.
i wonder why no one has suggested using RenderAction. you can write this Action method in you base controller. this will make it available in all derived controller. this way you can have your categories view strongly typed. Moreover, you should put your Categeories view in Views/Shared directory so every controller has access to this view. Doing so will keep you DRY and you still have the benefits of having strongly typed view.
EDIT By the way you don't have to have base controller to use renderaction. Although above approach is valid and i prefer doing like this but you can also have a nvaigation controller like
Public NavigationController:Controller()
{
public ActionResult Categories()
{
var Categories = FetchFromDB();
return View(Categoires);
}
}
Now you can call this action method using renderAction on anywhere in your application
You might want to try creating 2 display for templates, one to display in link and one to display in dropdown. Depending on the page you tell the view to use the specific template.
You can create a Filter that populates your categories and adds it to ViewData/ViewBag. You can then apply this filter to the controllers/actions that require the categories.
For displaying, you can use EditorTemplates or Partials to keep your UI code DRY...
HTH.

.NET MVC instantiate controller inside another controller

Is it possible for an ASP.NET MVC controller to create a new instance of a different controller and effectively delegate resonsibility to that?
Let's say for example that I have two controllers in the /Controllers/ directory:
public class HomeController : Controller
{
public ActionResult Index()
{
var otherController = new OtherController();
return otherController.ShowNumberOfThings(100);
}
}
public class OtherController : Controller
{
public ActionResult ShowNumberOfThings(int index)
{
return View(index);
}
}
...and a View called Views/Other/ShowNumberOfThings.aspx:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="ViewPage<int>" %>
Number of things: <%= Model.ToString() %>
When I hit the url:
http://localhost/Home/Index
I want to be presented with a page that reads:
"Number of things: 100"
I would like to be able to persist temporary data between controller redirections without being forced to use the session object (TempData[""] uses the session object for cross-controller redirections). My real world case has a complex object which needs passing (not just an int) so using a URL/Cookie is out of the question, and session state is a no-no.
In WebForms at least we could use Server.Transfer and maintain any state in the HttpContext.Items collection. In MVC the only option I can see is to call the controller method directly passing in required arguments.
At the moment it's having trouble trying to resolve the view folder as the "context" is still running under the HomeController.
I guess where I am going with this is trying to cludge ASP.NET MVC into acting like a FrontContoller.
Any ideas?
EDIT
In the end we had to serialise everything into a session and use that. A shame, but I have heard that MVC2 will support serialising objects into a ViewState.
If you want to be presented with "Number of things: 100" when you hit the Index action why not directly render the corresponding view:
public class HomeController : Controller
{
public ActionResult Index()
{
return View("~Views/Other/ShowNumberOfThings.aspx", 100);
}
}
I think it would be preferred to use.
return RedirectToAction("Controller", "Action")
However I'm guessing you want to maintain the Url Home/Index.
If you're looking at the FrontController pattern then you should investigate writing a Custom ControllerFactory which inherits from DefaultControllerFactory then Override the CreateController method.
You can register your factory using the code below.
protected void Application_Start()
{
ControllerBuilder.Current.SetControllerFactory(new MyCustomControllerFactory();
RegisterRoutes(RouteTable.Routes);
}
In the Controller factory you have access to the RequestContext so you can change the RouteData as needed and delegate to the correct controller.
You could of course just set a a Custom route for Home/Index which goes to OtherController.ShowNumberOfThings()
routes.MapRoute("Home", "Home/Index/{id}",
new {controller = "Other", action = "ShowNumberOfThings", id = 100});
a different approach would be the use of partial views
instead of ~Views/Other/ShowNumberOfThings.aspx
you could put your view in ~Views/shared/ShowNumberOfThings.ascx
have both views ~Views/Other/ShowNumberOfThings.aspx and ~Views/Home/Index.aspx implement the partial view
public class HomeController : Controller
{
public ActionResult Index()
{
return View(100);
}
}
public class OtherController : Controller
{
public ActionResult ShowNumberOfThings(int index)
{
return View(index);
}
}
and in both views implement the partial view
<% Html.RenderPartial("~Views/shared/ShowNumberOfThings.ascx", ViewData.Model); %>
you can change the int for any object that will be passed to the model
Another possibility (similar to partial views) is to use Html.RenderAction. This allows for different view model classes and separate controller methods.
<% Html.RenderAction("yourActionName", "yourControllerName", routeValues); %>

Resources