create custom controls mvc3 - asp.net-mvc-3

Just starting to get the hang of MVC3 and want to start building out some custom controls that I can just drop into a view. Specifically I want to be able to drop in some html form controls that will automatically add some javascript for validation.
something like this is what I'd want:
#Html.VTextBox("MyTextBox","",new {#vType="PhoneNumber", #vMessage="You Must Enter a Phone Number" })
when the page is rendered, I'd want it to search for any custom elements I create then drop in the appropriate javascript to validate client side. I did something very similar to this with asp.net web forms for custom controls ie
<ucc:VTextBox ID="MyTextBox" runat="server" message="You must fill this in" />
just wondering if MVC3 offers the same extensibility. Any resources or advice you can provide would be greatly appreciated.
TIA

To start off with, you'll need to look into extension methods and more specifically, how to create extension methods for the HtmlHelper class so you could write code like the example you've shown above.
For the example you've shown above, you could write code like this:
public MvcHtmlString VTextBox(string name, object value, object htmlAttributes)
{
StringBuilder html = new StringBuilder();
// Build your html however you want
// Here's a simple example that doesn't
// take into account your validation needs
html.AppendFormat("input type=\"text\" name=\"{0}\" value=\"{1}\" />", name, value);
return MvcHtmlString(html.ToString());
}
Then in your view you can use the example above like so:
#Html.VTextBox("MyTextBox","",new {#vType="PhoneNumber", #vMessage="You Must Enter a Phone Number" })
NOTE: You'll need to import the namespace the extension method is in to your view. Simplest method, put a #using ExtensionMethodNamespace at the top of your view. You can make the namespace automatically imported to all your views by fiddling with the web.config (and maybe the Global.asax).
ADDENDUM: Please note RPM1984's comment below where he advises to use TagBuilder in place of StringBuilder which is sound advice since this is the exact scenario TagBuilder was designed for. He also mentions strong typing the helper to the model which is also great advice.

Related

How do you format localized strings to have single words of a sentence be a link?

I am working on a globalized web-app in ASP.NET MVC3. The project contains the I18N resources files and I normally access the resources inside my Razor views like...
#I18N.MyResourceString
I have a tricky situation which I have not been able to figure out a elegant solution to yet. I need to be able to localized the sentence "Click here to donate." where the word 'here' should be a link to our donation system.
Most links in the site are internal so to create link I simply write...
#Html.ActionLink("Some link text", "MyAction", "MyController")
This donation link is external. What I have so far (which is not working) is...
#String.Format(I18N.ClickHereToDonate, "" + I18N.Here + "")
where the I18N.ClickHereToDonate resource's text is "Click {0} to donate.".
What I see on the screen is...
Click here to donate.
Furthermore, I would also like to add a 'title' attribute to the 'a' tag. It gets even uglier when I try that...
#String.Format(I18N.ClickHereToDonate, "" + I18N.Here + "")
There has to be a better way to form complex strings with embedded tags without concatenating things together in such a hackish manner. Not only does it not work (the intended markup got encoded) but it makes the HTML inside a string literal in my razor template which makes me loose any awesome IDE support/intergation/refactoring capabilities.
How can markup be injected into localized strings?
UPDATE
Adam Tuliper mentioned the #Html.Raw helper method in his answer so I added it to my already ugly markup...
#Html.Raw(String.Format(I18N.ClickHereToDonate, "" + I18N.Here + ""))
This at least got me a click-able link in the outputted markup.
Click here to donate.
It is still a far-less-than-elegant solution though so I am still looking for better ways of doing this.
Maybe try
#I18N.ClickHereToDonate
Overall, you don't need the String format - you can just inject the Razor things inside normal html elements.
Edit: Incorporating the below:
#Html.Raw(String.Format(#I18N.ClickHereToDonate,String.Format("<a href='http://paypal.com' title='{0}'>{1}</a>", I18N.PayPal,I18N.Here)))
Your choices are limited without built in support for this scenario (and there isn't in the helpers)
The cleaner way is to form your urls in a viewmodel and pass that view model to the view so you have minimal html. Your ViewModel contains
public class WhateverIndexViewModel
{
public string Key {get;set;}
public string URI {get;set;}
public string Title {get;set;}
}
Set the info in your controller, pass it to your view and use
#Links["YourKey"].Title"
As a basic idea.
Note if you dont want to use Html.Raw here then your URI in the class would be of type MvcString not String this way #Links["YourKey"].URI won't be html encoded.

MVC3 Finding a control by its Name

I have a C#.Net web app and I am trying to access one of the HTML/ASP Text Boxes in the Controller for the Edit View of my Proposal model. In a non-MVC app, I was able to do this using Control.ControlCollection.Find(). Is there an equivalent for a MVC3 project?
You ask for an equivalent of Control.ControlCollection.Find() in MVC?
In MVC your controller is not aware of controls.
The controller just receives data via parameters and returns data via the function result.
What do you want to do with the control in your controller code?
If you want to access the value, you should bind it to a parameter:
View:
<input name="MyControl" type="text" />
Controller:
public ActionResult MyAction(string MyControl) {
// MyControl contains the value of the input with name MyControl
}
The MVC pattern was designed to keep things separated.
The View has no knowledge of the controller at all
The Controller only knows that a view exists and what kind of data that it needs. It do not know how the data is render.
Hence, you can never get information about controls/tags in the view from the controller. You need to use javascript/jQuery in the view and invoke the proper action in the controller.
In an MVC-application you don't have controls like in a webform-application.
In MVC you collect your required data in the controller and pass it to the view.
Typicaly the view is a HTML-page with embedded code.
In opposite to controls in webforms which produce HTML and handles the post-backs in MVC you have to do all this manually. So you don't have controls with properties and events wich you can access easily in the controller and you have to handle all your posts with your own code.
Thats sounds as it is a lot of more work - and indeed it could be if you implement the behaviour of complex controls - but MVC applications are much better to maintain and you have 100% influence to the produced HTML.
Well probably i am late for this but it should help others in future...u can store ur value in hidden field in view and then access that value in controller by following code..
Request.Form["hfAnswerOrder"].ToString();
Point - hfAnswerOrder is the ID of the hidden field
My Control in cshtml page..
#Html.Hidden("hfAnswerOrder", Model.Answers.ToList()[0].AnswerOrder)

MailTo link in Razor

How do I make a mailto link using Razor?
I've seen Html.MailTo, but when I try #Html.MailTo nothing comes up.
Thanks!
You should just make a normal hyperlink:
...
Razor and MVC helper methods are not intended to replace HTML tags; they're intended to make common data-bound elements simpler.
HTML.MailTo() helper is a part of the 'mvc3 futures' project, but there is an alternative to way to do it.
1.)Create a new .cshtml file inside App_Code directory and name it as you want (for example HTMLHelpers.cshtml)
2.)Write the following in the file
#helper EmailTextBox(string email, string title) {
#title
}
3.)Now in your view you can call your new function. For example write
Email: #HTMLHelpers.EmailTextBox("george#example.com","George Chatzimanolis")
I get to this question when I was trying to do simple
Email
but Razor will treat this as text
I know there are HtmlHelpers and such but so far simple code worked for me
Email
Whether a variable comes from model or ViewBag it's about # sign in href and Razor
The mailto helper is a part of the 'mvc3 futures' project.
The below blog will give you more information on mvc3 futures as well as the link to get it. I believe that it is also available as a NuGet package.
http://weblogs.asp.net/imranbaloch/archive/2011/07/26/using-the-features-of-asp-net-mvc-3-futures.aspx
#{var email= new HtmlString( "" mail me ""));}
#email;

MVC3 View Inheritance not possible?

I want to create an abstract base view (with lots of virtual methods with cshtml)
and then create a derived view that optionally overrides these methods to customise the view:
for example:
override void Header(string title) {
<div class="massive">#title</div>
}
How can this be achieved with razor?
(doesn't everybody want/need to do this?)
I believe you would be better off using Helper methods than trying for an inheritance model on views. Use Scott Gu's blog for an introduction:
http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-helper-syntax-within-razor.aspx
http://www.asp.net/mvc/tutorials/creating-custom-html-helpers-cs
It doesn't quite work like that out of the box, although I'm sure with some effort you could get that to work.
Instead, you create Layouts with defined Sections and then derive other Layouts from those adding new sections if you need to. Then, a view will declare which layout it is using via
#{
Layout = "_Layout.cshtml" // app relative path to layout
}
and can provide markup for any sections as needed using
#section SectionName {
<p>I'm markup to go into a section in the layout this view is using</p>
}
You can pass data via ViewData and/or ViewBag, so you could use them to pass delegates if you wanted to do that.
Alternatively, you might decide add extension methods to HtmlHelper, UrlHelper or even create a WebViewPage derived from System.Web.Mvc.WebViewPage and add any additional properties/methods onto your derived type then set it as the pageBaseType in the <system.web.webPages.razor> in the web.config used by the views.
Simply use sections and layouts. You would define a layout containing some default contents into sections which could be overridden in child views. You could use the IsSectionDefined method in the layout to test whether a child view overrides this section and if not provide default content.
The cleanest way I found is to use a #helper declared in App_Code
which takes delegates as arguments:
#helper Example(Func<int, HelperResult> fn1, Func<int, HelperResult> fn2) {
<div>#fn1(100)</div>
<div>#fn2(200)</div>
}
and then create a view with helper functions:
#helper Custom1(int x) { <span class="small">#x</span> }
#helper Custom2(int x) { <span class="big">#x</span> }
and then invoke shared helper like this:
#Example(Custom1, Custom2)
and, if required, the shared helper can implement a default behaviour if the delegate is null
this is much messier than simply implementing a derived view with a few overriden virtual helpers - but at least it works, is strongly typed, and doesn't use dynamic

Inject selected view path as HTML comment to start and end of action's output

I'm currently putting together a multi-tenancy web app using MVC 3. At least 30 different Web sites will share a common codebase, and while also sharing similar under-the-hood functionality, they are need to look significantly different. As a consequence I'm using the URL to internally separate out clients, and an overridden Razor view engine (at least in terms of finding the view) to automatically select either a customised view for a given action, or the default view.
To help 'compartmentalise' a complex page, any particular view will make use of several partials, usually rendered as self-contained actions, so that generally a custom view or partial view will only have small HTML differences, helping to minimise any code specific to a client's site.
I hope you followed that background!
So one HTML page might be made up of lots of little smatterings of HTML partial views, which could come from specific folders for the client, or a general-purpose version. I'm hoping to make it easier for our designer to make minor changes to a page by easily seeing where in the folder structure the bit of HTML he wants to change are.
My proposal then is that each partial will be 'bracketed' with HTML comments such as:
{ Content of partial }
Obviously I could put these in manually, but that's just asking for trouble, for typos, for copied and then modified client versions not being updated with the correct URL. It should be possible to get this from some context and inject it, I think.
At the same time, I need to be able to not do this for certain Actions. Eg, a partial might be generating text inside a textarea, say, so the comments wouldn't be appropriate there. On the whole I'm happy to put these comments in unless I specify that it's not appropriate.
For me this suggests an ActionFilter on an Action, which I can apply site wide and then turn off for certain Actions. I'd hope that one of the overridable events would let me ascertain this path, but I can't seem to find anywhere it's stored. Furthermore, OnResultExecuting seems to fire before the Partial has been selected, and OnResultExecuted seems to have already written out the contents of the Partial, so I can't insert the starting comment here. I also can't find any reference to the path of the selected partial.
Just for completeness, it's my intention that this attribute would only write these comments when compiled in Debug mode.
So, does anyone know how I might be able to get the path to the selected View without any kind of hack between FindPartialView and the Attribute? Is my Attribute method the best choice or is there an easier way to do this? Perhaps something's built in already!
Many thanks for your help.
Well, I've never forgotten about wanting this, and always hoped I'd solve it one day, and thankfully I have.
I've overridden the default WebViewPage (I use the Razor engine), and in particular ExecutePageHierarchy to inject the comments:
public abstract class PaladinWebViewPage : PaladinWebViewPage<dynamic>
{
}
public abstract class PaladinWebViewPage<TModel> : WebViewPage<TModel>
{
public bool DisplaySourceCodeComments
{
get { return ((bool?) ViewBag.__DisplaySourceCodeComments) ?? false; }
set { ViewBag.__DisplaySourceCodeComments = value; }
}
public override void ExecutePageHierarchy()
{
base.ExecutePageHierarchy();
// Filters can be used to set and clear this value so we can decide when to show this comment
if (!DisplaySourceCodeComments) return;
var sw = Output as StringWriter;
if (sw == null) return;
var sb = sw.GetStringBuilder();
sb.Insert(0, string.Format("<!-- Start of {0} -->", VirtualPath));
sb.AppendFormat("<!-- End of {0} -->", VirtualPath);
}
VirtualPath tells us the exact file used to build the HTML, so we can inject the filename before and after. This isn't doing anything at the moment, since the default is to not show comments (the "?? false" in DisplaySourceCodeComments).
Also to use this view page you need to edit Views/Web.config and change the pageBaseType to this type.
I want to selectively turn these comments on and off so I've created an ActionFilter:
public class DisplaySourceCodeCommentsAttribute : ActionFilterAttribute
{
private readonly bool _displaceSourceCodeComments;
public DisplaySourceCodeCommentsAttribute(bool displaceSourceCodeComments)
{
_displaceSourceCodeComments = displaceSourceCodeComments;
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var viewResult = filterContext.Result as ViewResultBase;
if (viewResult == null) return;
viewResult.ViewBag.__DisplaySourceCodeComments = _displaceSourceCodeComments;
}
}
I'm slightly unhappy that I've had to use the ViewBag here and also separately in the view page override, as they aren't tightly linked, but I can't find a way for the filter to directly interact with the view page, so this is something of a necessary fudge. It does have the benefit that displaying source code for a view or partial also automatically displays it for any child partials until you turn it off again, since the ViewBag is passed down the chain.
With this in place, any action can turn on the source code comments with
[DisplaySourceCodeComments(true)]
or, obviously turn it off again with false
The Attribute checks that the context result is a ViewResultBase, which means just Views and Partials, so Json or Content or redirects aren't affected, which is very handy too.
Finally, I make this action filter a global when running in debug mode so that every view, and partial has the source comment included, by adding the following line to global.asax.cs:
[#]if DEBUG
// When in debug mode include HTML comments showing where a view or partial has come from
GlobalFilters.Filters.Add(new DisplaySourceCodeCommentsAttribute(true));
[#]endif
I'm really happy I've finally got it sorted so I hope this is useful for someone else.

Resources