Central HTML templates to prevent duplicate presentation code in ASP.NET MVC - asp.net

In my application I have a lot of UI elements (particuarly buttons) where I am repeating the same code over and over.
For example in multiple views I might have an edit button that is created with the following code:
#Html.ActionLink("Edit", "Edit", "SomeController", null, new { #class="button large blue" });
The problem here is that I am hard coding the label and styling, so if I want to make changes to the button styling or label, I would have to make them in multiple views. This would be tedious to do, as I would have to track down every button.
So now I am looking at creating a templating mechanism, where I can define a button template in a central area and bring it in to any view I want to use it in.
I have considered two options, which I have tried to sketch out in semi-real world code below.
My question is, am I on the right track here? Which option would be better and for which reasons? Is there already something out there I could consider using, or is there another way which I haven't thought of?
Thanks for your help.
Web.Config Templates
Create a custom class that can bring in template configuration from web.config file. For example:
<ui.HtmlTemplates>
<add templateKey="FormCancel" tag="a" class="form-button large black" />
<add templateKey="FormSave" tag="input" type="submit" class="form-button large green" />
</ui.HtmlTemplates>
And then could call them in with syntax such as this (method signature is contrived)
#HtmlTemplates.Build("FormCancel", Url.Action("Index", "Home"))
Partial View Templates
Create strongly typed partial views with the template I want.
ViewModel
public class UiButtonModel
{
public string Url{ get; set; }
}
Partial View
// Assume the file is called "_Button_FormCancel"
#Model path.to.model.directoy.UiButtonModel
Cancel
Use
#Html.Partial("_Button_FormCancel", new UiButtonModel(){Url = Url.Action("Index", "Home"));

Another option is to create extension methods off of HtmlHelper to create prepackaged HTML output using code:
using System.Web.Mvc;
using System.Web.Routing;
public static class MyHtmlExtensions
{
public static string SaveButton(this HtmlHelper helper, string title = "Save", IDictionary<string, object> htmlAttributes = null)
{
var builder = new TagBuilder("button");
builder.Attributes.Add("type", "button");
builder.AddCssClass("form-button");
builder.AddCssClass("large");
builder.AddCssClass("green");
if (htmlAttributes != null) builder.MergeAttributes(htmlAttributes);
builder.SetInnerText(helper.Encode(title));
return builder.ToString();
}
public static string CancelButton(this HtmlHelper helper, string title = "Cancel", string actionName, string controllerName, RouteValueDictionary routeValues = null, IDictionary<string, object> htmlAttributes = null)
{
var urlHelper = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection);
var builder = new TagBuilder("a");
builder.Attributes.Add("href", urlHelper.Action(actionName, controllerName, routeValues));
builder.AddCssClass("form-button");
builder.AddCssClass("large");
builder.AddCssClass("green");
if (htmlAttributes != null) builder.MergeAttributes(htmlAttributes);
builder.SetInnerText(helper.Encode(title));
return builder.ToString();
}
}
Then just make sure the namespace of MyHtmlExtensions is either added to your page directly, or included in all pages via web.config, and use it like this in your view (razor syntax):
<div class="form-buttons">
#Html.CancelButton("Index", "Home")
#Html.SaveButton()
</div>
This method is particularly well suited for creating output consistently across several solutions, as all you need to do is reference the containing assembly and import the namespace.

I create these kinds of templates and put them in my Views/Shared folder.
I have templates like:
AddButton.cshtml
DeleteButton.cshtml
SaveButton.cshtml
...
Then, when I need to call one of them in whatever View, I just call this for example:
#Html.Partial("SaveButton");
Using T4MVC, it gets even better with compile time checking (no more literal strings):
#Html.Partial(MVC.Shared.Views.SaveButton)
Doing so I have a common/central place to change a specific button config. No need to go view after view to change something.

This is the problem that css was designed to handle. I fail to understand the problem. If you want to make changes, you change the CSS and it affects all the buttons that have that styling.
Part of your problem is that you're using style like "blue". If you want to change it to red, you have to change it everywhere.
Instead, you should have a class for the button, then you can simply change the button style and you don't have to worry about redefining blue to red.

Related

Using ViewBag in Layout page

I have my _Layout.cshtml file that uses the ViewBag model to render some dynamic content.
I understand ViewBag can be populated in the controller and accessed in the view and/or layout page.
My question is, if my Layout page is using #ViewBag.SiteName, I want to avoid having to set this variable in each controller before I return the view. Is there a way to set this variable on a global level? Or how else should I pass this data to the layout page?
If you set anything in ViewBag - this happens after the Layout has been rendered -
You've missed the boat.
As others have mentioned, you can create a "helper" controller:
public class LayoutController : BaseController
{
[ChildActionOnly]
public ActionResult SiteName()
{
return new ContentResult {Content = "Site name goes here"};
}
}
Then, in your layout:
#{Html.Action("SiteName", "Layout")}

#model for _layout.cshtml on MVC4?

I was wondering if there's a way to specify a model for the _layout.cshtml file, i've seen lots of posts with the basic same question with people replying with "alternative" solutions, not saying it's not possible nor showing how exactly we could achieve this
having some experience with webforms I've been trying to migrate to MVC and often find myself with such questions, I've found this website: http://blog.bitdiff.com/2012/05/sharing-common-view-model-data-in.html
which partially solved my problem but even them don't bind their _layout.cshtml with a #model, as far as I know, I have to specify a model on each view if I want to access the SharedContext, please correct if I'm wrong
what I wanted to do is declare a "#model Namespace.MyModel" on _layout.cshtml so it could retrieve its information by itself, instead of having to implement a model for each view inherinting from the LayoutModel
*I hope I'm being clear, basically, I wanted to know how can I declare #model tag on a _layout.cshtml so it can access its own model
with the solution I linked before (even though it's not linked to my question) I have to do:
#(((BaseController)ViewContext.Controller).Context.Property) to get the shared information, and if I could simply declare (and use) a #model instead, I could accomplish the same thing by doing something like: #Model.Property*
as you can see, im struggling trying to migrate whatever I already know from webforms to MVC and it's being quite difficult for me since I have to adopt certain practices which are completely different from what I'm used to
thanks in advance
You should delegate the parts of your layout that "need a model" to a separate controller using partial views and RenderAction:
#Html.RenderAction("SomeAction", "LayoutController")
Have LayoutController.SomeAction return a PartialViewResult, which you can then strongly type to a model.
Even though you already accepted an answer, based on your saying you are just pulling an image URL you should do it using JQuery, not a model.
This code is untested, apologies for that. Feel free to point out if I typed a bug. The HTML element containing the background image has the id="url" attribute so the selectors work.
Controller
[HttpGet]
public string GetSessionUrl()
{
//logic to detmine url
return url;
}
JQuery
$(document).ready(function () {
var $url = $('#url');
var options = {
url: "/Home/GetSessionUrl",
type: "get",
async:false
};
$.ajax(options).done(function (data) {
$url.attr('src', data);
});
});
You can add BaseModel to _Layout.
#model BaseModel
Then all models inherit from that BaseModel class.
public class MyModel : BaseModel
{
}
As others stated, it is not a good practice. If your model forgets to inherit from BaseModel, it'll throws exception at run time. However, it is up to you.
In BaseController you can declare any model as property.
public class BaseController : Controller
{
public BaseController ()
{
MyTag = new TagModel (); // or get db, take any value from there
}
public TagModel MyTag { get; set; }
}
In action:
ViewBag.MyTag = MyTag ;
And in _Layout.cshtml, you can use
#{
var myTag = (TagModel)ViewBag.MyTag;
}

How can I run code from my layout file?

I used the following tutorial to help me build an RSS Reader in my ASP.NET MVC3 Razor application:
http://weblogs.asp.net/jalpeshpvadgama/archive/2011/08/17/creating-basic-rss-reader-in-asp-net-mvc-3.aspx
However, unlike the tutorial example, I want the RSS feed to be displayed on every page, and have therefore added it to my layout file, /Views/Shared/_Layout.cshtml
I currently only have 2 views on my site, and to get the RSS Reader to work on both views I've got the following code in my HomeController:
public class HomeController : Controller
{
//
// GET: /Index/
public ActionResult Index()
{
return View(CT.Models.RssReader.GetRssFeed());
}
public ActionResult About()
{
return View(CT.Models.RssReader.GetRssFeed());
}
}
From my WebForms experience, I would simply add the RSS Reader code in my master page code behind, and it would automatically work on every page.
Is there a Controller for layout pages which allows me to do the same?
How can I get this to work on every call of the layout page, without having to return anything?
EDIT: Following #Sebastian's advice, I've now added this code to a Partial View, removed CT.Models.RssReader.GetRssFeed() from return View() and included this in my layout file:
#Html.Partial("_MyPartialView")
The code in this partial view is:
<ul>
#foreach (var item in Model)
{
<li>
#item.Title
</li>
}
</ul>
However, I'm not getting a runtime error:
Object reference not set to an instance of an object.
It's erroring on the line #foreach (var item in Model)
You have to create a partial view and add functionality there.
Then in your layout, render this partial.
EDIT
Is your partial view really a partial view? The reason I said that is because you have "_" in front of the name which suggests that it might be a layout (might just be a naming convention).
To fix object reference error, you have to add the #Model declaration on top of your partial view.
Hope it helps.
UPDATE
In order to use different model in partial view, you need to explicitly declare which model you are going to use on render partialmethod.
#{Html.RenderPartial("../YourFeed", Model.YourFeedModel);}
Let me know if that resolved your issue.
The new error you are having is due to you not passing a Model to the partial view. You can do this with the second argument of the Html.Partial function...
Html.Partial("ViewName", MyModel);
As I think you are trying to do this in a Layout page you could also consider using a static reference to get your RSS feed. So forget about needing to pass in a Model and in your partial have:
#foreach (var item in RssRepository.GetFeed())
{
<li>
#item.Title
</li>
}
this like to a class something like...
public static RssRepository
{
public static MyModel GetFeed()
{
return new MyModel();//<- return what you would normally pass as a Model for RSS feeds
}
}
Hope that all makes sense

asp mvc controller creation

i want dynamically create ascx files, to partial render them.
but as i know, ot show them , i at least need dummy method:
public ActionResult test()
{
return PartialView();
}
how can i create this method for each new ascx file?
upd: i need factory?
Why would you create dynamic ascx files?
If you want to create all the layout in the controller you should be able to return it directly.
But then, why would you do that?
This way it will be really hard to do unit testing and refactoring and reuse.
You'd need to create your .ascx controls ahead of time. If you are doing this, I would recommend that you register a new view engine to provide a new PartialView location.
public class MyViewEngine : WebFormsViewEngine
{
public MyViewEngine()
{
PartialViewLocationFormats = new[]
{
"~/Views/{1}/{0}.ascx",
"~/Views/GeneratedControls/{0}.ascx",
"~/Views/Shared/{0}.ascx"
};
}
}
This allows you to write your dynamic views to the /Views/GeneratedControls/ folder. If you need to use a specifically named control (i.e. the control you generate has a random name) then you simply need to adjust your call to PartialView:
public ActionResult test()
{
return PartialView("name-of-control");
}
Otherwise MVC will use the name of the Action as the name of the control to use.

ASP.NET MVC2: modifying master css property depending on query string parameter

I am migrating a web site to a new one using ASP .NET MVC2.
In the original site, master page has code-behind to check a query string parameter value. Depending on this value, code-behind dynamically modify some CSS property to hide / display master page elements.
As MVC2 has no code-behind because we are supposed to perform everything in the controllers, how should I proceed in this case ?
I see this : asp.net mvc modifying master file from a view
It partially answers my needs but the query string processing is common to all pages. How can I move this processing in a common code section ?
Regards.
A helper method looks like a good place:
public static class HtmlHelperExtensions
{
public static string GetCss(this HtmlHelper htmlHelper)
{
// read some request parameter
// here you also have access to route data so the
// parameter could be part of your custom routes as well
var foo = htmlHelper.ViewContext.HttpContext.Request["foo"];
// based on the value of this parameter
// return the appropriate CSS class
return (foo == "bar") ? "barClass" : "fooClass";
}
}
And somewhere in your master page:
<body class="<%= Html.GetCss() %>">
Or if you are always going to apply it to the body tag only it might be more appropriate to do this in order to reduce the tag soup:
public static class HtmlHelperExtensions
{
public static MvcHtmlString StartBody(this HtmlHelper htmlHelper)
{
var body = new TagBuilder("body");
var foo = htmlHelper.ViewContext.HttpContext.Request["foo"];
var bodyClass = (foo == "bar") ? "barClass" : "fooClass";
body.AddCssClass(bodyClass);
return MvcHtmlString.Create(body.ToString(TagRenderMode.StartTag));
}
}
and in your master page at the place of the body tag:
<%= Html.StartBody() %>
I can think of two solutions to this:
Derive your controllers from one controller base and set the ViewData parameter there depending on posted Form values
Don't use ViewData at all, but simply look for the form value in the view (using HttpContext.Current)
The second method violates the MVC pattern. IMO it is still acceptable in some scenarios, for example I am using this approach to highlight the currently selected item in a navigation menu.

Resources