I'm trying to create a templated control with Asp.Net MVC. By templated control, I mean a control that accepts markup as input like so:
<% Html.PanelWithHeader()
.HeaderTitle("My Header")
.Content(() =>
{ %>
<!-- ul used for no particular reason -->
<ul>
<li>A sample</li>
<li>A second item</li>
</ul>
<% }).Render(); %>
Note: Yes, this is very similar to how Telerik creates its MVC controls, I like the syntax.
Here's my PanelWithHeader code:
// Extend the HtmlHelper
public static PanelWithHeaderControl PanelWithHeader(this HtmlHelper helper)
{
return new PanelWithHeaderControl();
}
public class PanelWithHeaderControl
{
private string headerTitle;
private Action getContentTemplateHandler;
public PanelWithHeaderControl HeaderTitle(string headerTitle)
{
this.headerTitle = headerTitle;
return this;
}
public PanelWithHeaderControl Content(Action getContentTemplateHandler)
{
this.getContentTemplateHandler = getContentTemplateHandler;
return this;
}
public void Render()
{
// display headerTitle as <div class="header">headerTitle</div>
getContentTemplateHandler();
}
}
This displays the ul, but I have no idea how to display custom code within my Render method.
I have tried using the HtmlHelper with no success. I have also tried overriding the ToString method to be able to use the <%=Html.PanelWithHeader()... syntax, but I kept having syntax errors.
How can I do this?
public void Render()
{
Response.Write(getContentTemplateHandler());
}
It turns out that the Telerik MVC extensions are open-source and available at CodePlex so I took a quick look at the source code.
They create an HtmlTextWriter from the ViewContext of the HtmlHelper instance. When they write to it, it writes to the page.
The code becomes:
// Extend the HtmlHelper
public static PanelWithHeaderControl PanelWithHeader(this HtmlHelper helper)
{
HtmlTextWriter writer = helper.ViewContext.HttpContext.Request.Browser.CreateHtmlTextWriter(helper.ViewContext.HttpContext.Response.Output);
return new PanelWithHeaderControl(writer);
}
public class PanelWithHeaderControl
{
private HtmlTextWriter writer;
private string headerTitle;
private Action getContentTemplateHandler;
public PanelWithHeaderControl(HtmlTextWriter writer)
{
this.writer = writer;
}
public PanelWithHeaderControl HeaderTitle(string headerTitle)
{
this.headerTitle = headerTitle;
return this;
}
public PanelWithHeaderControl Content(Action getContentTemplateHandler)
{
this.getContentTemplateHandler = getContentTemplateHandler;
return this;
}
public void Render()
{
writer.Write("<div class=\"panel-with-header\"><div class=\"header\">" + headerTitle + "</div><div class=\"content-template\">");
getContentTemplateHandler();
writer.Write("</div></div>");
}
}
*I know, the code is a mess
You might want to do something like Html.BeginPanel() / Html.EndPanel(), similar to how forms are created with Html.BeginForm() / Html.EndForm(). This way you can wrap the contained content rather than need to pass it as a parameter.
Related
I search for an option to render some div-tags in my cshtml files with razor conditionally like the angular directive ng-if.
Normally I write code like
#if (mycond) {
<div>blub</div>
}
And I search for a more readable approach like
<div asmarttag:mycond>blub</div>
Is there any option to achieve that?
Not sure if it's exactly what you want... but what about a custom HTMLHelper?
namespace System.Web.Mvc
{
public static class CustomHTMLHelpers
{
public static IHtmlString Render(this HtmlHelper helper, bool render, string html)
{
if (render)
{
return helper.Raw(html);
}
else
{
return new HtmlString("");
}
}
public static IHtmlString Render(this HtmlHelper helper, bool render, MvcHtmlString html)
{
if (render)
{
return html;
}
else
{
return new HtmlString("");
}
}
}
Then you could do:
#Html.Render(mycond, "<div>blub</div>")
or
#Html.Render(true, #Html.ActionLink("Home", "Index", "Home"))
Would certinly turn it into a one liner for you.
I would like to add a condition to a html beginform.
If the condition is false, I dont want the form tag to be generated.
Similar to this code:
public static MvcHtmlString If(this MvcHtmlString value, bool evaluation)
{
return evaluation ? value : MvcHtmlString.Empty;
}
I recently had to do something similar for a one off feature. This is proof of concept code but it looked something like:
public class NoForm : IDisposable
{
// Whatever this suppose to look like
// but actually does nothing
public void Dispose() { }
}
In the view:
#{
IDisposable form;
if (Model canShowForm)
{
form = Html.BeginForm(...);
}
else
{
form = new NoForm()
}
}
#using (form)
{
#* we might be in a form *#
}
Obviously this could be refactored into an HtmlHelper extension method, I just haven't done it.
I have a simple tag builder that looks like this:
public static MvcHtmlString Tag(this HtmlHelper helper, string tag, string content)
{
var tagBuilder = new TagBuilder(tag){InnerHtml = content};
return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.NormalTag));
}
And, I can use it like this:
#Html.Tag("em", Model.Title)
which produces:
<em>The Title</em>
How can this be written to use a Fluent Syntax so it's use would look like this:
#Html.Tag("em").Content(Model.Title)
You have to define a builder interface and implementation. I hope my example can provide some guidance:
public static class MyHtmlExtensions
{
public static IMyTagBuilder Tag(this HtmlHelper helper, string tag)
{
return new MyTagBuilder(tag);
}
}
Then you define your builder interface and implementation:
public interface IMyTagBuilder : IHtmlString
{
IHtmlString Content(string content);
}
public class MyTagBuilder : IMyTagBuilder
{
private readonly TagBuilder _tagBuilder;
public MyTagBuilder(string tag)
{
_tagBuilder = new TagBuilder(tag);
}
public IHtmlString Content(string content)
{
_tagBuilder.InnerHtml = content;
return this;
}
public string ToHtmlString()
{
return _tagBuilder.ToString(TagRenderMode.NormalTag);
}
}
Since IMyTagBuilder implements IHtmlString, it can be used either with or without calling .Content() afterwards.
A great trick to use when implementing fluent interfaces it to use a IFluentInterface to hide object members (ToString, Equals, GetHashCode and GetType) from IntelliSense, it removes some noise.
EDIT: A great resource for building fluent APIs is Daniel Cazzulino's screencast from building Funq here
I am learning the ASP.NET MVC 3 Framework. In my layout page (_Layout.cshtml), I would like to conditionally include some CSS stylesheets depending on the name of the controller. How do I do that?
You could obtain the current controller name using the following property:
ViewContext.RouteData.GetRequiredString("controller")
So based on its value you could include or not the stylesheet:
#if (ViewContext.RouteData.GetRequiredString("controller") == "somecontrollername")
{
<link href="#Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
}
Or use a custom helper:
public static class CssExtensions
{
public static IHtmlString MyCss(this HtmlHelper html)
{
var currentController = html.ViewContext.RouteData.GetRequiredString("controller");
if (currentController != "somecontrollername")
{
return MvcHtmlString.Empty;
}
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
var link = new TagBuilder("link");
link.Attributes["rel"] = "stylesheet";
link.Attributes["type"] = "text/css";
link.Attributes["href"] = urlHelper.Content("~/Content/Site.css");
return MvcHtmlString.Create(link.ToString(TagRenderMode.SelfClosing));
}
}
and in layout simply:
#Html.MyCss()
I would use different approach. Define base controller instead and define method SetStyleSheet like:
public abstract class BaseController : Controller
{
protected override void Intialize(RequestContext requestContext)
{
base.Initialize(requestContext);
SetStyleSheet();
}
protected virtual void SetStyleSheet()
{ }
}
In derived classes you can override SetStyleSheet to set something like ViewData["styleSheet"] and use it for example in your master page (_Layout.cshtml).
Darin definitely answered your questions but an alternative would be use the controllers name as the id of some HTML element on your page, which would give you the flexibility of customizing controller-level views but keep your CSS in one file.
<body id="<%=ViewContext.RouteData.GetRequiredString("controller").ToLower() %>">
... content here
</body>
I did another extension method for ControllerContext because ViewContext is aleady derived from it and you can call your method directly.
For example :
public static class ControllerContextExtensions
{
public static string GetControllerName(this ControllerContext helper)
{
if (helper.Controller == null)
{
return string.Empty;
}
string[] fullControllerNames = helper.Controller.ToString().Split('.');
return fullControllerNames[fullControllerNames.Length-1].Replace("Controller",string.Empty);
}
}
And to use this in your _Layout :
#if(ViewContext.GetControllerName() == "MyControllerName")
{
//load my css here
}
You could also pass in your controller name as parameter and make this extension method return a bool.
I wish to display content depending on the given role(s) of the active user , in the ASP.NET MVC.
Compare the old fashion way, using WebForms:
protected void Page_Load(Object sender, EventArgs e)
{
if(User.IsInRole("Administrator")) {
adminLink.Visible = true;
}
}
Now how would I go on writing that when using the ASP.NET MVC ?
From my point of view, it would be wrong to place it directly in the View File, and assigning a variable for every single view won't be pretty either.
Create Html helper and check current user roles in its code:
public static class Html
{
public static string Admin(this HtmlHelper html)
{
var user = html.ViewContext.HttpContext.User;
if (!user.IsInRole("Administrator")) {
// display nothing
return String.Empty;
// or maybe another link ?
}
var a = new TagBuilder("a");
a["href"] = "#";
a.SetInnerText("Admin");
var div = new TagBuilder("div") {
InnerHtml = a.ToString(TagRenderMode.Normal);
}
return div.ToString(TagRenderMode.Normal);
}
}
UPDATED:
Or create wrapper for stock Html helper. Example for ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName):
public static class Html
{
public static string RoleActionLink(this HtmlHelper html, string role, string linkText, string actionName, string controllerName)
{
return html.ViewContext.HttpContext.User.IsInRole(role)
? html.ActionLink(linkText, actionName, controllerName)
: String.Empty;
}
}
No you would be placing it in the view file, like so actually:
<% If (User.IsInRole("Administrator")) { %>
<div>Admin text</div>
<% } %>
this worked for me:
<% MembershipUser mu = Membership.GetUser();
if (mu != null)
if (Roles.IsUserInRole(mu.UserName, "Administrator"))
{
%>
<li class="paddingleftThree"><%= Html.ActionLink("User Administration", "GetUsers", "Account")%></li> <%} %>
The separation of concerns approach suggested in ASP.NET MVC 4 How do you serve different HTML based on Role? in my opinion is a better way to go.
Personally I avoid IsInRole check as much as possible in the code and leave it to declarative means to achieve role based restriction as much as possible. This ensures code remains maintainable over time. I am not sure if this is a right or the wrong approach, but has worked well for me.