Our designers have come up with button styles for an application which require the addition of <span> tags inside the <a> tags of our links.
In ASP.NET we implemented this by adding an App_Browsers entry for Link Buttons.
How would I go about doing this in ASP.NET MVC?
I've contemplated creating my own versions of all of the various HTML helper functions for creating ActionLinks and RouteLinks but this seems to be quite a 'brute force' way of doing things.
Is there a nice elegant way of doing it?
I know we could write some simple jQuery to do it, but we'd rather have the markup coming out of the server correctly in the first place.
Actually I think writing a new helper is exactly the way I would go. Seems to me that that's exactly what they are there for and it makes them very re-usable too.
You could always write one extension method, that takes another one (one of the built-in ones) as an argument, and wrappes the <span> around your link text before calling it. It should be quite easy to do with lambdas...
public static string SpanLink(this HtmlHelper helper,
string linkText, object args, Action<string> action)
where TController : IController
{
action("<span>" + linkText + "</span>", args);
}
And to call it:
<%= Html.SpanLink<HomeController>("link text", (s) => Html.ActionLink<HomeController>(c => c.Index(s));
(This code is typed directly into the answer field of SO - I haven't even checked it to make sure it compiles. So bear with me if it doesn't work on the first try...)
Related
Consider the following string if I need to localize it:
You need to write <b>#ViewBag.CharacterAmount</b> characters to be able to
hand-in this homework. You're still missing <b id="charactersRemaining">
#ViewBag.CharacterAmount</b> characters to reach this limit.
What would be the best approach? Using string.Format is a bit complex, since ASP .NET MVC escapes HTML code, and besides, I'd rather be free of HTML code in my resource files. However, I still need to be able to refer to those values inside the b tags from JavaScript.
Any ideas? What is your approach to this when you do localization?
You could write a custom helper:
public static class ResourcesExtensions
{
public static IHtmlString Resource(this HtmlHelper htmlHelper, string message, params object[] args)
{
var parameters = args.Select(x => htmlHelper.Encode(x)).ToArray();
return new HtmlString(string.Format(message, parameters));
}
}
As you can see the HTML helper encodes only the values. We have full control over the rest of the message because it is in the resources file and we suppose that it is valid HTML, so no problem with XSS.
and then have a resources file to your project which will contain for example the following key:
MyMessage = You need to write <b>{0}</b> characters to be able to hand-in this homework. You're still missing <b id="charactersRemaining">{1}</b> characters to reach this limit.
and then don't forget to mark this resources file with the PublicResXFileCodeGenerator custom tool so that Visual Studio generates a public class that will allow you to access the properties in the view.
and finally in the view:
#Html.Resource(Resources.MyMessage, (int)ViewBag.CharacterAmount, (int)ViewBag.CharacterAmount)
The reason you need to cast is because extension method cannot dispatch dynamic arguments. But obviously that's not a problem at all because you shouldn't be using ViewBag/ViewData but you should be using view models and strongly typed view models so in your real code you will have:
#Html.Resource(Resources.MyMessage, Model.CharacterAmount, Model.CharacterAmount)
One downside with this approach is that we have moved some markup in the resources file which unfortunately might render the views a little less understandable and when we need to modify it, we should do this in all localized versions.
Another approach of course consists into putting in your resources file every distinct part of this markup and then:
#Resources.YouNeedToWrite <b>ViewBag.CharacterAmount</b> #Resources.StillMissing
<b id="charactersRemaining">ViewBag.CharacterAmount</b> #(Resources.ToReachLimit).
i have searched the web relentlessly for this and have not found anything - which is surprising because i would think it is such a common scenario!
Basically, on my model i have a DateTime field which i wish the user to populate through a form. I am using the Html helper to render all other parts of the form (along with validation)
So this question is in two parts...
Html Helper
Firstly, is there any way to use the Html helper to split the DateTime field to be rendered as the three constituent parts of a date: day, month, year (since i do not care about the time part). This could be rendered as text boxes, drop down lists or a combination of both.
Model Binding
And then when the form is posted, what is the best approach for binding back up to the model? I have seen Scott Hanselmann's solution to this, but it seems a little bloated for what i need - i was hoping for a slightly more elegant solution. Is it recommended to extend DefaultModelBinder and set that as default binder (since all dates would be handled in this way) or write a class that implements IModelBionder and set it as the default binder for the DateTime type?
Thanks for all the help in advance :-) i'm loving MVC but it's infuriating me that something so trivial is causing so much headaches!
think i've worked out a decent solution to this, so i will provide an answer for any who stumble across this in future!
With regards to the Html helper, i somehow completely overlooked creating an extension method! So eventually when it occurred to me, i wrote an extension method which basically makes calls to other methods of the Html helper to provide three fields (whether you use drop down of text inputs is your choice)
public static string DateTime(this HtmlHelper helper, string name)
{
StringBuilder builder = new StringBuilder();
string dayName = name + ".Day";
string monthName = name + ".Month";
string yearName = name + ".Year";
builder.Append(helper.TextBox(dayName));
builder.Append(helper.DropDownList(monthName, typeof (Months)));
builder.Append(helper.TextBox(yearName));
return builder.ToString();
}
As for the binding after a form post, i simply created a class which implemented IModelBinder and set it as default for DateTime type on application start. This can then be overridden at a controller action level should a different method of binding be required. This was essentially a simpler version of Scott's binder which i linked to in my question.
Works like a charm at the moment (if a little simplistic), but i'm sure it'll be enough of a foundation for anyone else who is mystified by this problem!
Main question: Is there a better way to accomplish creating a reusable control?
So the idea was to make a paging control to basically stop from having to keep typing out practically the same markup on multiple views. It's taking this:
<%= Html.ActionLink("First", "Details", new RouteValueDictionary(new { parentForumId = Model.TopicId, pageNumber = Model.FirstPage, amountToShow = Model.AmountToShow }))%>
|
<%= Html.ActionLink("Previous", "Details", new RouteValueDictionary(new { parentForumId = Model.TopicId, pageNumber = Model.PreviousPage, amountToShow = Model.AmountToShow }))%>
|
<%= Html.ActionLink("Next", "Details", new RouteValueDictionary(new { parentForumId = Model.TopicId, pageNumber = Model.NextPage, amountToShow = Model.AmountToShow }))%>
|
<%= Html.ActionLink("Last", "Details", new RouteValueDictionary(new { parentForumId = Model.TopicId, pageNumber = Model.LastPage, amountToShow = Model.AmountToShow }))%>
And turning it into this:
<%= Html.Pager("View", "Controller", "RouteName", Model, new Dictionary<String, Object> { {"parentForumId", Model.ParentForumId}}, " ") %>
Where as you can see I pass in the needed view, controller, route name, model, and a dictionary used to add request variables onto the url for the link.
What I found is that I would have to make an extension method for the HtmlHelper class and essentially take what in ASP.Net was a full class (with nice methods like CreateChildControls) and jam it all into one main method that returns a string.
Is this the preferred way of doing this? One nice thing of the ASP.Net way was markup to class as in you have the html markup tag that would translate markup properties to class properties. It generally made for cleaner mark up but admittedly "fake" html. In this situation I have a method with what could be a mile long signature pumping out html. And since I don't have a base WebControl class, every control I make will have to have method calls with the same basic needs like say CssClass or ID.
Now with that being said, I suppose I could pass in an attributes dictionary since the
HtmlHelper.GenerateRouteLink
method that I'm using calls for one anyhow, but this really seems really messy.
Is there a better way to do this?
First, its all ASP.NET...one is MVC, the other is WebForms. Took me a sec to realize what you were saying when you keept saying the "ASP.NET way". :P
The idea with an MVC is that your view is "dumb", without any real behavior outside of the absolute bare bones basics to render data. In WebForms, views were tightly bound to the behavior that rendered them and handled view events. This, while convenient, made WebForms views very difficult to unit test since view content and behavior were linked and sometimes blended.
The reason MVC views use things like HtmlHelper and AjaxHelper is to keep behavior as separated from the view as possible. Unlike a user or server control in WebForms, you can fully unit test an Html.Pager extension method, since the logic is pure code, without blending those UI concerns or being linked to a bunch of non-testable UI level types. The same general rule applies to MVC controllers to...they are just code, without being linked to events or anything like that.
It may be less convenient in the short run, as you are currently used to the old WebForms way of doing things. Give yourself some time, though, and you will likely start to realize the benefits that MVC's preferred way of doing things brings to the table. Writing a Pager extension method on HtmlHelper is indeed the preferred way to do things with MVC.
As for the mile-long signature bit...do a search (try out Bing.com!) for fluent style interfaces and HtmlHelper. The fluent style is starting to take a strong hold in environments like MVC views where you are likely to have huge signatures. The general idea is based on method chaining, kind of like jQuery, and can shorten those long signatures into a series of much shorter and more meaningful chained method calls that set up your html helper, with a final call to a .Render method or something similar.
You could put it in a partial view, instead of creating a helper.
You might want to check out Martijn Boland's Pager control for some inspiration.
Personally, for my reusable grid control I use a class that contains all information needed to generate a grid with paging, sorting, ... and I call partial views to generate the seperate elements (Pager, Column selection, pagesize selection, ...), passing the information they require to them.
This way I can easily extend the grid with custom stuff. For example I can create a Mygrid_editableTable.ascx view to show textboxes instead of just text, and add an extra column with a submit button. This while continuing to use the paging, page selection, ...
We end up using html helpers for paginators as they are easy to unit test. Pagination business requirements can be finicky.
"Show less than 35 links as numbers, then group by 20s unless there are more than 100 pages of results, in which case group by 100s...but on Thursdays, or to GoogleBot, show them as... etc."
Plus our SEO guys keep changing their mind on what shape urls get the most juice. In such a situation, something unit testable is a must!
Ok, this is for me a very tough challenge. We're taking our existing ASP.NET website and converting (redesigning the PL only) to MVC. Our site is very complex. But the hard part is to convert the existing custom controls to MVC equivilant. The custom controls (I am not talking about user controls) are just of course a class currently that inherits System.Web.UI.Control and uses that object throughout. For example, we have some properties at the top of this existing custom class like so:
Dictionary<int, Control> configControls;
DropDownList kControl;
CheckBox confirmBox;
These all are variables of type Web controls in classic ASP.NET.
So I figured maybe what I could do (without building entire new custom controls from scratch) is to use the HtmlHelper object. So I tried this:
(include first the using statement that includes System.Web.MVC.Html at the top of my new custom class in our new web project)
private HtmlHelper helper;
Dictionary configControls;
helper.DropDownList
but this is not working. I guess I can't use this object just like this ?? I figured I can use HtmlHelper in the Dictionary and then make variable types off of helper. but those are just extension methods, not objects
I don't know of an equivalent to something like the generic "Control" we had available to us to inherit from such as in classic ASP.NET. Surely it won't be the same in MVC obviusly (stateless and a completely diff way of doing things) but what can I use in MVC with the same concept sort of?
So I figured maybe what I could do (without building entire new custom controls from scratch) is to use the HtmlHelper object. So I tried this:
(include first the using statement that includes System.Web.MVC.Html at the top of my new custom class in our new web project)
private HtmlHelper helper;
Dictionary configControls;
helper.DropDownList
but this is not working. I don't even know if this approach will work in my custom control. And when I try to use my helper variable, I get no extension methods unless it's inside an existing extension method where the signature has an HtmlHelper param passed in. So when I create that private variable just in my custom class outside, I get nothing in intellisense to choose from when doing "helper.". So do I need to define that object like this: ?
private HtmlHelper htmlHelper = new HtmlHelper();
but it's asking for a ViewContext and an IViewDataContainer as params. If I'm building out a custom method that knows nothing yet about its view (it shouldn't need to) because I'm simply creating strings of HMTL in this custom class to be passed to the Extension method to ultimately spit out fields then maybe I can't use HtmlHelper this way in a custom class like this.
So can I use that object in a way instead of "Control"? Maybe I can even in my dictionary variable use type object in place of control ? I don't know and then cast object to type HtmlHelper when I need to use or reference that value from the dictionary? But for now, I figured I can use HtmlHelper object in the Dictionary and then make variable types off of helper. but those are just extension methods, not objects.
I hope I am making any sense here when you read this.
I just blogged about this last night, some of this might be helpful for you.
WebForms And MVC In Harmony — Almost…
Basically it discusses some options for emulating "WebControls" using MVC.
Additionally, you can still use WebControls like you could before (granted they may not work if they need things like the ViewState). The problem I've discovered with that is you have a disconnect from the inline render code and the WebControls themselves.
I did write this method last night which let you use WebControls with inline code.
using System.Reflection;
using System.IO;
using System.Web.UI;
using System.Web;
using System.Web.Mvc;
public static class MyExtensionMethods {
//example method - renders a webcontrol to the page
public static void RenderControl(this HtmlHelper helper, Control control) {
//perform databinding if needed
MethodInfo bind = control.GetType().GetMethod("DataBind");
if (bind is System.Reflection.MethodInfo) {
bind.Invoke(control, null);
}
//render the HTML for this control
StringWriter writer = new StringWriter();
HtmlTextWriter html = new HtmlTextWriter(writer);
control.RenderControl(html);
//write the output
HttpContext.Current.Response.Write(writer.ToString());
//and cleanup the writers
html.Dispose();
writer.Dispose();
}
}
//then used like...
<% int[] numbers = { 1, 2, 3, 4, 5 }; %>
<% this.Html.RenderControl(new DataGrid() { DataSource = numbers }); %>
Just an interesting concept you might be interested in.
Short of hacking webforms controls into your MVC application, servercontrols with many methods do not map to MVC.
They are replaced by partials and controllers(or subcontrollers if you like that sort of thing).
If all you want to do is render some HTML based on a few parameters, then a Helper is what you are after. Static Class, static methods. If however, you need to keep state, and do a bunch of stateful stuff, then a partial, JS, and controller(or subcontroller) are really what you are after.
Server Controls that manage their own state really are a thing of the past in MVC.
Remember that MVC is an attempt to use the web the way it was meant to work, particularly if you bring REST into the picture. Webforms is a fudge to make the web work like windows forms.
I would create needed business logic, shared partial view (probably, with quite a lot of well structured javascript lines attached) and seperated controller.
Then i would use this bunch of code through partial request technique.
Not sure how much this will be of help but, do have a look at this series of blog post
Custom controls everywhere
Also have a look at the Catharsis project
Web-Application Framework - Catharsis - Part I - New Solution
The codeplex URL for the same is
Catharsis
This project has some good examples of control creating for asp.net mvc.
What is the replacement for a server control in ASP.NET MVC? What I want to do is to create a declarative and imperative binding so I can write
<cc1:MyControl Header="Some Header" Content="Some Content" />
which would mean that an instance of the MyControl class will be created and possibly rendered to
<h1>Some Header</h1>
<p>Content</p>
I don't want any viewstate or postback crap, just the modularity. I also want these modules to be contained in a separate class library, so ViewUserControls will not do for me. Using a server controls in the normal way works, but it generates a form tag and a viewstate field, which I do not want if I can avoid it.
I have seen this question and this one about how to use server controls in ASP.NET MVC, but they do not provide enough answer.
Edit: I found the answer. When I added the user control using the designer, it automatically created a <form> which I missed. If I simply remove that tag, everything works perfectly.
You can still use all controls in ASP.NET MVC if they don't require rendering in a server form.
ascx files and #Register directives still work pretty well. The great new thing is Html.RenderPartial method that lets you pass a model object to a partial view (ascx) and have it render accordingly.
Just adding one more possibility to Mehrdad answer, you can use extension methods to do a simple control like this:
<%= html.MyControl( "Some header", "Some content" ) %>
<Extension()> _
Public Function MyControl(ByVal htmlHelper As HtmlHelper, _
ByVal Header As String, _
ByVal Content As String) As String
Dim sb As New StringBuilder()
sb.AppendFormat("<h1>{0}</h1>", Header)
sb.AppendFormat("<p>{0}</p>", Content)
Return sb.ToString()
End Function
Or you can make a more complex control like this example: Create an ASP.NET MVC GridView Helper Method
Other than the controls which still work with ASP.Net MVC, you can use mvc controls.
Repeater example - dead link
Exploring ASP.Net MVC Futures - dead link
UPDATE: This answer was for ASP.Net MVC 1.0 in 2009. It is outdated and irrelevant at this point.