Duplicating code in MVC controllers - asp.net-mvc

I think I have a problem in understanding the proper way of using MVC. The problem I'm having is that I have Users and Admin Users, both are allowed to create a campaign but they are using different master pages etc etc.
My solution so far is...
Controllers
AdminUserController.cs
UserController.cs
Views
AdminUser
CreateCampaign.aspx
User
CreateCampaign.aspx
But in doing it in this way I'm having to duplicate the CreateCampaign() code in both the AdminUserController and the UserController and I have 2 views to do the same thing.
Is this the right way of doing it or am I missing the boat somewhere?

Extract the common code to a base controller from which both inherit. Extract the shared view to a common, shared partial view (ViewUserControl), then have each view include this shared partial. The latter is really only necessary since your view uses different master pages.
Controllers:
BaseUserController
CreateCampaign()
UserController : BaseUserController
AdminController : BaseUserController
Views:
Shared
CreateCampaignShared.ascx
Admin.Master
User.Master
Admin
CreateCampaign.aspx -- includes <% Html.RenderPartial( "CreateCampaignShared" ); %>
User
CreateCampaign.aspx -- includes <% Html.RenderPartial( "CreateCampaignShared" ); %>

You can do pretty well with a single controller, leave it to UserController. Admin is just another user, right? In your CreateCampaign() code you can check for "special" status of the logged in user and set some extra properties before saving the data.
Whether you can get away with a shared view depends on how much they vary. You can use simple IsAdmin() checks in the view to render or not some extra controls. Or you can check it in the controller and serve one view or another.

Why do you have two different "Users"? I'd prefer one user-Class and roles to provide access to different views / actions
Then you would create a Campain-Controller and there a CreateCampaign-Action.

You're missing the partial boat :)
You can create one CreateCampaign.ascx file that shares the common view code and call it as a partial view in some other view. Instead of creating full views, create a partial view (an .ascx file), and have it contain the duplicate code and markup.
Then you can re-use it in other views with this:
<% Html.RenderPartial("CreateCampaign") %>
Re-use your controller code by factoring it out into some base controller which your specific controllers inherit from.

How about having a CampaignController that has the Create method which then displays different views depending on the type of user. Something like:
public class CampaignController : Controller {
public ActionResult Create() {
//...
if (User.IsInRole("Admin") {
Return View("AdminUser/CreateCampaign");
}
else {
Return View("User/CreateCampaign");
}
}
}
And as others have said the duplicated markup/code in the views should be seperated into partial views and then RenderPartial() used:
<% Html.RenderPartial("Campaign") %>

If it's as simple as displaying a different master to users who are admins you can set the master page to use for the View from the controller as follows:-
return View("CreateCampaign", User.IsInRole("Admin") ? "Admin", "User");
This should allow you to have a single Campaign controller and Create view which seems more natural to me than controllers dedicated to a particular type of user (which sounds like an implementation detail).

Related

In a big and complex ASP.NET MVC application is created a model of all other model classes?

I'll explain my point:
The best practice is to create views strongly typed with a Model. You only can stronly type one Model.
If you need two models in a view you can created two views and use Partial Render, but it seems not to be the very best option.
Another approach is to create another type model that encapsulates the other pieces of the model what you need; this make much more sense for me.
Then, my question is, in a complex proyect when a page needs to communicate with all the models and they are not direct realted, developers create a type that encapsulates all the other things?
For non-related parts of your view, you may use Html.Action() to invoke an action that returns a partial view.
This way, the logic of the "area" will be encapsulated in its own action and/or controller.
Update: I don't know if it's really the best practice, but I prefer composition over complex views & view models. Even for related information, I prefer to break it to smaller partial views and child actions. As I see it, it has the following flexibility:
Ability to easily move some of the partial views/child actions to a layout page
Load the partial view asynchronously via AJAX query
Reduced controller action complexity and increased maintainability.
Better support for conditioned rendering
Separation of concerns
In (4) I mean that you can easily do the following without complicating your view model:
<div class="header">
#if (loggedInUser.ShowAds) {
#Html.Action("Header", "Ads")
}
</div>
Answering the question in your comment.
Considering twitter. There's the content pane and the users box on the left.
So here's our TweetsController:
public class TweetsController: Controller {
public ActionResult Index() {
var tweets = ...;
return View(tweets);
}
}
The Tweets/Index view may look like:
#model Tweet[]
<div class="leftPane">
#Html.Action("Index", "Users");
</div>
<div class="mainContent">
#foreach var t in Model {
#t.User - #t.Text
}
</div>
Note that the left pane just calls the Index action in UsersController to display the users list.
Here's how it may look like:
public class UsersController: Controller {
public ActionResult Index() {
var users = ...;
return PartialView(users);
}
}
And here's the partial view (Users/Index):
#model User[]
#foreach var u in Model {
<img src="#u.IconUrl"/> #u.Name
}
So what will actually happen, when the Tweets view will be rendered Html.Action will put the partial view returned by UsersController.Index in the left pane.
Of course you may move this logic into a layout if this behavior is common for a number of pages.
Hope that helps.
Then, my question is, in a complex proyect when a page needs to
communicate with all the models and they are not direct realted,
developers create a type that encapsulates all the other things?
Yes some of them do, and the name for these is ViewModels

Logic for Partial Views Used Across an App

How do I associate a PartialView which will be used across the app with a Child Action? For example the PartialView could be a login bar at the top of the page which will need to have some associated logic (loading the model etc) which I would normally put in a ChildAction.
However, I don't know what Controller will be used. I think I could create a base Controller class with the ChildAction and then inherit from that but I was hoping there would be a more elegant solution.
The RenderAction method allows for selecting the controller:
http://msdn.microsoft.com/en-us/library/ee839451.aspx

Pass data to User Control ASP.NET MVC

I have a user control which shows list of latest announcements. This user control would be used in almost 90% of my pages. Now my concern is how to pass data to this user control for latest announcements.
My first approach is to make a base controller and in Initialise method I pass data to user control via ViewBag/ViewData. All my other controllers derive from this base controller. This looks nice but my concern is that it may become an overkill for some simple solution existing already out there. Also I would need to make sure that no controller ever fiddles with my Viewdata/Viewbag data meant for my usercontrol.
Please let me know if this is correct way of proceeding ahead or there exists some better solution.
Thanks
Assuming you have a "user control" (you should try to refer to them as partial view's in MVC) that looks like this:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Announcement>>" %>
This means your partial view expects a list of Announcement objects.
Now, the question is - where are you rendering this partial view?
You could be doing it from a master page, you could be doing it from a view, or you could be doing it from another partial view.
Either way, the code to render the partial needs to look like this:
<% Html.RenderPartial("LatestAnnouncements", announcements) %>
But - where do you get the announcements from.
Assuming you have a Repository/DAL/helper method to get the latest announcements - i think you should have the ViewModel's you require inheriting from a base ViewModel:
public class AnnouncementViewModelBase
{
protected IEnumerable<Announcement> GetAnnouncements()
{
// call DAL
}
}
Then any master/view/partial that needs to render the latest announcements partial should be bound to a ViewModel which inherits from that base view model.
In the cases where the master/view/partial is not strongly-typed (e.g dynamic view), you can stick it in the ViewData. But if you have organized your view's correctly this shouldn't be required.
Is this the kind of thing you're after? How to pass data from view to UserControl in ASP.NET MVC?
You should use RenderAction in this kind of scenario, so that you do not have bother to pass the required data in each action method of your controllers.
I think the best way would be to use #Html.Action. This would allow me to call my actions dedicated to my usercontrols data and I can call it from anywhere.

Where to apply logic for a sidebar control in ASP.NET MVC

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

View Models (ViewData), UserControls/Partials and Global variables - best practice?

I'm trying to figure out a good way to have 'global' members (such as CurrentUser, Theme etc.) in all of my partials as well as in my views.
I don't want to have a logic class that can return this data (like BL.CurrentUser) I do think it needs to be a part of the Model in my views So I tried inheriting from BaseViewData with these members. In my controllers, in this way or another (a filter or base method in my BaseController), I create an instance of the inheriting class and pass it as a view data. Everything's perfect till this point, cause then I have my view data available on the main View with the base members. But what about partials?
If I have a simple partial that needs to display a blog post then it looks like this:
<%# Control Language="C#" AutoEventWireup="true" Inherits="ViewUserControl<Post>" %>
and simple code to render this partial in my view (that its model.Posts is IEnumerable<Post>):
<%foreach (Post p in this.Model.Posts) {%>
<%Html.RenderPartial("Post",p); %>
<%}%>
Since the partial's Model isn't BaseViewData, I don't have access to those properties. Hence, I tried to make a class named PostViewData which inherits from BaseViewData, but then my containing views will have a code to actually create the PostViewData in them in order to pass it to the partial:
<%Html.RenderPartial("Post",new PostViewData { Post=p,CurrentUser=Model.CurrentUser,... }); %>
Or I could use a copy constructor
<%Html.RenderPartial("Post",new PostViewData(Model) { Post=p }); %>
I just wonder if there's any other way to implement this before I move on.
Any suggestions?
Thanks!
Have you considered keeping these things in the session and writing a strongly-typed wrapper around the session that will give you access to this information? Then in any view you can simply create a new wrapper class with the ViewPage's (or ViewUserControl's) Session property and use it.

Resources