I have an ASP.Net MVC 4 application where the user can choose a theme or a design of for their hosted one-page site (within this application). At first, I thought to do this with the built-in Areas but due to some application restrictions I've decided not to use that method. The way I thought to do it (which so far works) is to send the user to the index action of the controller, there find out which theme they have chosen and then return the appropriate view. this way I don't have the action name on the url which is nice since the url needs to be simple like: abc.com/cb/websiteID. btw, every theme/design has one view in the folder.
For some reason this method does not sit well with me and I think there should be a better way of doing this. Is there a downfall to this? Is this method a bad practice? is there a better way?
If I've left out a detail, please let me know and I'll do my best to address it.
Do you have a limited set of themes, which your users can choose from?
If so, I would consider to have a layout-per-theme instead, have a single view and dynamically switch layout based on params...
//in your controller
public ActionResult(int id) {
string layoutForThemeName = SomeService.GetThemeForUser(id);
ViewBag.LayoutName = layoutForThemeName
}
// in your view Index.cshtml
#{
Layout = ViewBag.LayoutName;
}
Do not forget that Razor let you inherit one layout from another, so you could event create base layout with script references etc. and layout for every of your themes.
Related
Which is the best way to create custom controls in a Big Mvc application? Partials views which means a partial view for each control ? HTML Helpers? another alternative?
please find below a sample of custom control that I have in the asp web application:
protected override void Render(HtmlTextWriter writer)
{
writer.WriteLine("<div class=\"btnContainer\">");
base.Render(writer);
writer.WriteLine("</div>");
writer.WriteLine("<div class=\"clr\"></div>");
}
Partials alone aren't really "controls". They need something to drive them. In MVC 5 and previous you can use child actions to create something very similar to an ASCX control. In MVC 6+ View Components are the way to go.
You can also use an HtmlHelper extension. Really, the chief determination of which you should use boils down to whether you need external data access. If you need to query a database, hit a Web API, etc., then use child actions / view components. If you're self contained (i.e. you just want to render a bit of HTML based on some existing value you already have, then you can go with an HtmlHelper extension. However, a child action / view component still works in this scenario, as well, meaning they're much more versatile overall.
Should we move logic that supposes to be in Controller (like the data to render the partial view) to ActionFilter?
For example, I'm making a CMS web site. There should be a advertisement block to be rendered on several pages but not all the pages. Should I make an ActionFilter attribute like [ShowAd(categoryId)] and decorate the action methods with this attribute?
The implementation of this controller would include service calls to retrieve information from database, buildup view models and put in the ViewData. There would be a HtmlHelper to render the partial view using the data in ViewData if it exists.
That just seems yucky to me.
When I'm trying to figure out whether I need an ActionFilter, the first question I have is, Is this a cross-cutting concern?. Your particular use-case doesn't fit this, at first blush. The reason is, is that an ad is just another thing to render on a page. There's nothing special about it that makes it cross-cutting. If you replaced the word 'Ad' with 'Product' in your question, all the same facts would be true.
So there's that, and then there's the separation of concerns and testability. How testable are your controllers once you have this ActionFilter in place? It's something else you've got to mock out when testing, and what's worse is that you have to mock out those dependencies with every controller you add the ActionFilter to.
The second question I ask is, "How can I do this in a way that seems most idiomatic in the platform I'm using?"
For this particular problem, it sounds like a RenderAction and an AdController is the way to go.
Here's why:
An Ad is its own resource; it normally isn't closely tied to anything else on the page; it exists in its own little world, as it were.
It has its own data-access strategy
You don't really want to repeat the code to generate an Ad in every place you could use it (which is where a RenderPartial approach would take you)
So here's what such a beast would look like:
public AdController : Controller
{
//DI'd in
private AdRepository AdRepository;
[ChildActionOnly]
public ActionResult ShowAd(int categoryId)
{
Ad ad = Adrepository.GetAdByCategory(categoryId);
AdViewModel avm = new AdViewModel(ad);
return View(avm);
}
}
Then you could have a custom partial view that is set up around this, and there's no need to put a filter on every action (or every controller), and you don't have try to fit a square peg (an action filter) in a round hole (a dynamic view).
Adding an Ad to an existing page then becomes really easy:
<% Html.RenderAction("ShowAd", "Ad" new { categoryId = Model.CategoryId }); %>
If your ad system is simple enough, there is no reason you could/should not use an action filter to insert enough info into the view data to generate the ad in your view code.
For a simple ad system, say.. a single ad of a specific category shows up in the same place in the layout on every page and that's it, then there is no real argument of a better way except to prepare for future changes to the system. While those concerns may be legitimate, you may also have it on good authority that requirement will never change. But, even if requirements do change, having wrapped all the code that generates ads in one place is the most important aspect and will save you much more time up front than a more robust solution might. Obviously there are more than a few ways to wrap this code in a single place.
As for the way you are choosing to do it, I would keep your action filter cleaner to only have it insert the category into the view data and have all the magic happen inside your html helper which would take the category in as a parameter. Building up view models to shove into the view data is going to require a bit of extra work, and put code all over the place when it doesn't need to be there. Keep it simple and do all of the html generation inside of the html helper which is responsible for...building html.
Take the example of wanting to have a "Latest news items" sidebar on every page of your ASP.NET MVC web site. I have a NewsItemController which is fine for pages dedicating their attention to NewsItems. What about having a news sidebar appear on the HomeController for the home page though? Or any other controller for that matter?
My first instinct is to put the logic for selecting top 5 NewsItems in a user control which is then called in the Master Page. That way every page gets a news sidebar without having to contaminate any of the other controllers with NewsItem logic. This then means putting logic in what I understood to be the presentation layer which would normally go in a Controller.
I can think of about half a dozen different ways to approach it but none of them seem 'right' in terms of separation of concerns and other related buzz-words.
I think you should consider putting it in your master page. Your controller can gather data (asynchronously, of course), store it in a nice ViewModel property for your view (or in TempData) and then you can call RenderPartial() in your master page to render the data.
The keeps everything "separate"
http://eduncan911.com/blog/html-renderaction-for-asp-net-mvc-1-0.aspx
This seems to address the question - even using the instance of a sidebar - but using a feature not included with MVC 1 by default.
http://blogs.intesoft.net/post/2009/02/renderaction-versus-renderpartial-aspnet-mvc.aspx
This also indicates the answer lies in RenderAction.
For anyone else interested, here's how I ended up doing it. Note you'll need to the MVC Futures assembly for RenderAction.
Basically you'd have something like this in your controller:
public class PostController
{
//...
public ActionResult SidebarBox()
{
// I use a repository pattern to get records
// Just replace it with whatever you use
return View(repoArticles.GetAllArticles().Take(5).ToList());
}
//...
}
Then create a partial view for SidebarBox with the content you want displayed, and in your Master Page (or wherever you want to display it) you'd use:
<% Html.RenderAction<PostController>(c => c.SidebarBox()); %>
Not so hard after all.
You can create a user control (.ascx) and then call RenderPartial().
Design a method in your controller with JsonResult as return type. Use it along with jQuery.
Use RenderAction() as suggested by elsewhere.
News section with ASP.NET MVC
My question may be obvious but I'd like to build a well-designed web application.
As for any administration area, the admin should be able to list/create/delete/modify users, articles, posts, etc...
I'd like to know what is the best way to design the application.
Should I create a controller for each one of those items (/Users/Create/id or /Posts/Delete/id), or create all of the action in my Administration Controller (/Administration/CreateUser/id or /Administration/DeletePost/id) ?
You should write a separate controller for each entity to keep a clean separation of concerns for your controller classes. If you only have one controller, then you will only have one Views directory with dozens of views, and your controller will contain dozens of methods, and that will soon become unmanageable.
The answer depends on how much functionality will be in the controllers. Just start of with one controller and if it gets too much split it into a few.
The great thing about MVC is where you put things in your controllers doesn't have to have any effect on the URLs. you can very easily map /Users/Create to e.g. UserAdminController class.
I would just build a new MVC website that handles the administration.
You have a more flexible solution as long as you've separated the data and the business logic in different assembly's. You could then publish your website to a subdomain, admin.yoursite.com for example. That way you don't have to mess with your routes and you can keep them in separate views which imho is the most elegant solution.
Pro's and Con's would be nice to hear.
I'm working on a project which will need the same administration site but haven't got that far yet so this question interests me a lot.
I'm currently using ASP.NET for a large client.
The approach I've adopted is to put the functionality of the action into another class.
Example
I am writing an administration section also. There will be one Administration controller (our admin section is small, if it was larger I would change the routing to allow more controllers, for now we are using the out of the box configuration). If I create an "EditUser" view. I will also create an "EditUserAction" class. All EditUser code will go into the class. I construct the EditUserAction class in the Administration controller class in the Edit User method. This removes all the action specific code out of the Controller class. This way all the action specific code is either in the action method or in the action class. Otherwise, the controller would quickly become overrun with code from various actions. The controller class would balloon to an unmanageable mess in short order.
Class examples
public class Administration: Controller
{
public ActionResult EditUser(string userId)
{
EditUserAction action = new EditUserAction();
}
}
public class EditUserAction
{
public void Save(User user)
{
//save code here
}
}
I hope this explanation is clear. If it's not let me know and I'll clarify.
To answer your question I am doing it the latter(/Administration/CreateUser/id or /Administration/DeletePost/id).
You could use DynamicData for this. It isn't MVC but it can be used together with it and it is really easy to setup and use.
Here is another way of asking my question.
A part of my Master Page:
<% if (!String.Equals(ViewContext.RequestContext.RouteData.GetRequiredString("controller"), "Administration")) { %>
<div>
<!-- Some Code -->
</div> <% } %>
As you can see, in my master page, I'd like to display some part of the page, depending on the user working on the administration area or not.
It works pretty well with only the Administration Controller (/Administration/CreateUser/id)... but it becomes a big mess when I use different controller as User or Article (/User/DeleteUser/id or /Article/Details/id).
I'd prefer to use one controller per entity, but I can't find a way to bond this approach with multiple controllers.
I suggest to use this solution.
But I changed definition to this:
public ThemedViewEngine()
{
base.MasterLocationFormats = new string[] {
"~/Views/{1}/{0}.master",
"~/Views/Shared/{0}.master",
"~/Themes/{2}/Views/{1}/{0}.master",
"~/Themes/{2}/Views/Shared/{0}.master",
"~/Themes/Default/Views/{1}/{0}.master",
"~/Themes/Default/Views/Shared/{0}.master"
};
base.ViewLocationFormats = new string[] {
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx",
"~/Themes/{2}/Views/{1}/{0}.aspx",
"~/Themes/{2}/Views/{1}/{0}.ascx",
"~/Themes/{2}/Views/Shared/{0}.aspx",
"~/Themes/{2}/Views/Shared/{0}.ascx",
"~/Themes/Default/Views/{1}/{0}.aspx",
"~/Themes/Default/Views/{1}/{0}.ascx",
"~/Themes/Default/Views/Shared/{0}.aspx",
"~/Themes/Default/Views/Shared/{0}.ascx"
};
base.PartialViewLocationFormats = new string[] {
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx",
"~/Themes/{2}/Views/{1}/{0}.aspx",
"~/Themes/{2}/Views/{1}/{0}.ascx",
"~/Themes/{2}/Views/Shared/{0}.aspx",
"~/Themes/{2}/Views/Shared/{0}.ascx",
"~/Themes/Default/Views/{1}/{0}.aspx",
"~/Themes/Default/Views/{1}/{0}.ascx",
"~/Themes/Default/Views/Shared/{0}.aspx",
"~/Themes/Default/Views/Shared/{0}.ascx"
};
}
Default theme is default so it is required to exists.
Structure of directory will be:
Content
Themes
Default
Content
Views
Home
Blog
Whatever should be skinned
OtherTheme
Content
Views
Home
Blog
Whatever should be skinned
Views
Articles
Posts
Users
Settings
Other Administration stuff
It depends on the scale size of your admin area,
I suggest you consider to do the following (or maybe document it a bit)
Consider how many entities you want to manage independently,
Consider how many actions each one will have,
Check that is there any dependency between your application and admin area (user access, for user friendly URLs )
then you can specify which approach can help you, Having one admin controller, admin actions in entity controllers, or defining a new Admin project in case of large functional applications.
*If the project scale is growing fast and soon it needs large scale, I would choose third one - having a new admin mvc project.
I hope it help you decide.
On the main page of my site, I would like to show several views which rely on their own controllers for data retrieval. I do not want to retrieve anything from the DAL in my Home controller.
For example, I want to show view listing top 5 news, a view with random quote from the database, another view with the users shopping cart contents, etc.
After Googling around, I found RenderAction method which is almost perfect, but it's not available in RC1, only in Futures, and apparently, it has some issues.
I found RenderPartial as well, but that relies on the main controller to pass data to the view.
Additional clarification:
The main reason I do not want data access logic in the Home controller is to avoid repeating the code and logic. I will use top 5 news view in several pages/controllers. I do not want to repeat data retrieval in every one of them. I already did separate a lot of logic and validation to business layer. The solution I'm after is RenderAction or UserControls as in classic ASP. I know i can use them in MVC as well, but... whats the point? I mean, if what i'm asking is too complicated or too absurd (reusable UI components), then MVC is definitely not for me, and I'd consider it seriously inferior to classic ASP.NET, because this requirement is really simple.
What you're asking is to basically not perform data access in the HomeController, this seems like a dogmatic approach. I would consider either using RenderAction from the Futures assembly (not sure what's wrong with it, I use it in a number of projects) or SubControllers from MvcContrib.
While I can understand the desire not to replicate functionality in multiple controllers, I don't understand the reluctance to have your Home controller interact with the DAL. I think the partial view is definitely the way to go. My solution to not replicating the functionality would be to push the code that generates the data for the various views into your business or data layer. You could then reference it from each of the required controller actions that use the partial views. Putting it in the business layer could isolate the controller from your data layer, if that's what you desire, but I still think it's the proper job of the controller action to obtain and provide the data to the view.
Another potential solution would be to populate the view generated by your Home controller via Ajax callbacks to the various controller actions that generate the required view components. The drawback to this is that it doesn't fail gracefully in the absence of javascript in the browser.
EDIT
Based on your clarification, I would suggest implementing a base controller that fills the ViewData for the shared controls in ActionExecuted (so that it's done only when the action succeeds). Derive your other controllers from the base controller when you want to inherit this behavior.
If you really don't want to use RenderAction, then the only other option you have is to load the necessary data pieces with action filters. Your home controller could then look like this:
public class HomeController : Controller
{
[RequireNews]
[RequireQuotes]
[RequireCart]
public ActionResult Index()
{
return View();
}
}
These action filters could be re-used where they are needed. You might also choose to place these on the controller class itself.