Best Method to Get a String from a Razor View - asp.net-mvc

I would like to have my Razor View return a string of the html it renders, so that the controller would return the rendered html string from the View and not just the view. There is no native method I can find in ASP.NET MVC to do this. Are there any workarounds for this?
By way of illustrative example :
public ActionResult Index()
{
return View().ToString; //View has no ToString() method but this is what I am trying to do
}

You can use Html.Partial to return an MvcHtmlString of the view.
You can find the Partial method in System.Web.Mvc.Html.PartialExtensions.
There's more information here about this method: http://msdn.microsoft.com/en-us/library/ee402898.aspx
I personally use this for my RenderPartials (plural) extension method:
public static void RenderPartials<T>(this HtmlHelper helper,
string partialViewName, IEnumerable<T> models, string htmlFormat)
{
if (models == null)
return;
foreach (var view in models.Select(model =>
helper.Partial(partialViewName,model, helper.ViewData)))
{
helper.ViewContext.HttpContext.Response
.Output.Write(htmlFormat, view);
}
}
Update
To render your view to a string in your controller you can do something like this (although it seems like a bit of a hack):
var htmlHelper = new HtmlHelper(new ViewContext(), new ViewPage());
var viewString = htmlHelper.Partial("PartialViewName");
The reason I say it's a bit of a hack is because HtmlHelper is designed to be used in your views and not in your controllers or models. That being said, if it works and there isn't an alternative to stringify a parsed view it might be of use to you.
Given the amendment to your question you'd be looking for something like this:
public string Index()
{
var htmlHelper = new HtmlHelper(new ViewContext(), new ViewPage());
return htmlHelper.Partial("PartialViewName");
}
If the above code doesn't create the htmlHelper correctly you can create it like this instead:
TextWriter writer = new StringWriter();
var htmlHelper = new HtmlHelper(new ViewContext(ControllerContext,
new RazorView(ControllerContext, "","",true,null),
new ViewDataDictionary(),
new TempDataDictionary(), writer), new ViewPage());

Related

Instantiate a helper class but not in view

I need a way to instantiate a class thats supposed to help me with some querystring-building when it comes to my links in the view, and I cant use this methods that i require as static methods since that would cause the querystringbuilder to keep data during the whole life-time of the application (which would cause some serious problems)
So my question to you guys is,
Is it possible to some how be able to instantiate the class/object that i require but not in the actual view itself?
SO to keep the question simple.. is there anyway that I could do something like:
#MyInstantiatedObject.DoStuff()
in my view with out doing this before in my view:
#{
var MyInstantiatedObject = new MyClass()
}
I do get that I some where some how will need to instansiate the object, but my question is if its possible to do it in some other manner (like telling the web.config to handel it..or using some app_code #helper magic or something)
Thanks in advance!
What you are trying to achieve goes against the philosophy of MVC. If you want to keep the query string data between the actions, you can create your custom ActionLink html helper like this:
public static MvcHtmlString ActionLinkWithQueryString(this HtmlHelper htmlHelper,
string linkText, string actionName)
{
var routeValueDictionary = new RouteValueDictionary();
return htmlHelper.ActionLinkWithQueryString(linkText,
actionName, routeValueDictionary);
}
public static MvcHtmlString ActionLinkWithQueryString(this HtmlHelper htmlHelper,
string linkText, string actionName, RouteValueDictionary routeValues)
{
var queryString = HttpContext.Current.Request.QueryString;
if (queryString.Count > 0)
{
foreach (string key in queryString.Keys)
{
routeValues.Add(key, queryString[key]);
}
}
return htmlHelper.ActionLink(linkText, actionName, routeValues);
}
You can also create a custom RedirectToAction method in your Controller or in a Controller Extention like this:
private RedirectToRouteResult RedirectToActionWithQueryString(string actionName)
{
var queryString = Request.QueryString;
var routeValues = new RouteValueDictionary();
foreach (string key in queryString.Keys)
{
routeValues.Add(key, queryString[key]);
}
return RedirectToAction(actionName, routeValues);
}

ASP.NET MVC, Querystring, routes and default binder, how do I combine this?

I have an ASP.NET MVC site that uses strongly typed views. In my case, a controller action could look like this:
public ActionResult List(MyStrongType data)
When submitting the page (view) the response will generate a URL that looks something like this (yes, I know routing could generate a nicer URL):
http://localhost/Ad/List?F.ShowF=0&ALS.CP=30&ALS.L=0&ALS.OB=0&ALS.ST=0&S=&LS.L1=&LS.L2=&CS.C1=32&CS.C2=34&CS.C3=&ALS.ST=0
If I submit the page again, I can see that the data object in the action is set properly (according to the URL)(default binder).
The problem is: Say that I am to add page buttons (to change the page) for a list on my sitepage, the list will be controlled by settings like filter, sortorder, amount of pages per page and so on (controlled by the querystring). First, I need to include all current query parameters in the URL, and then I need to update the page parameter without tampering with the other query parameters. How can I genereate this URL from the view/"HTML helper"?
I could of course manipulate the URL string manually, but this will involve a lot of work and it will be hard to keep up to date if a route is changed, there must be a easier way? Like some kind of querystring collection that can be altered on service side (like ASP.NET Request.QueryString)?
I would hope to not involve the route, but I post the one I got so far anyway:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"TreeEditing",
"{controller}/{action}/{name}/{id}",
new { controller = "MyCategory", action = "Add", name = string.Empty, id = -1 }
);
BestRegards
Edit 1: It's possible to set the query parameters like this (in view):
<%= url.Action(new {controller="search", action="result", query="Beverages", Page=2})%>
But this will only generate a URL like this (with the default route):
/search/result?query=Beverages&page=2
The rest of the parameters will be missing as you can see.
I could of course add every known parameter in this URL action, but if any query parameter is added or changed there will be a lot of work to keep everything up to date.
I have read the article ASP.NET MVC Framework (Part 2): URL Routing, but how do I find an answer to my problem?
It sounds to me like the problem you have is that you want to be able to easily persist query string values from the current request and render them into the URLs of links in your view. One solution would be to create an HtmlHelper method that returns the existing query string with some changes. I created an extension method for the HtmlHelper class that takes an object and merges its property names and values with the query string from the current request, and returns the modified querystring. It looks like this:
public static class StackOverflowExtensions
{
public static string UpdateCurrentQueryString(this HtmlHelper helper, object parameters)
{
var newQueryStringNameValueCollection = new NameValueCollection(HttpContext.Current.Request.QueryString);
foreach (var propertyInfo in parameters.GetType().GetProperties(BindingFlags.Public))
{
newQueryStringNameValueCollection[propertyInfo.Name] = propertyInfo.GetValue(parameters, null).ToString();
}
return ToQueryString(newQueryStringNameValueCollection);
}
private static string ToQueryString(NameValueCollection nvc)
{
return "?" + string.Join("&", Array.ConvertAll(nvc.AllKeys, key => string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(nvc[key]))));
}
}
It will loop through the query string values from the current request and merge in the properties defined on the object you passed in. So your view code might look like this:
<a href='/SomeController/SomeAction<%=Html.GetCurrentQueryStringWithReplacements(new {page = "2", parameter2 = "someValue"})%>'>Some Link</a>
This is basically saying "keep the query string from the current request, but change the page and parameter2 values, or create them if they didn't exist." Note that if your current request has a "page" query string parameter, this method will overwrite the value from the current request with the one you explicitly pass in from the view.
In this case, if your querystring was:
?parameter1=abc&page=1
It would become:
?parameter1=abc&page=2&parameter2=someValue
EDIT:
The above implementation will probably not work with the dictionary lookup of querystring parameter names you described. Here is an implementation and that uses a dictionary instead of an object:
public static string UpdateCurrentQueryString(this HtmlHelper helper, Dictionary<string, string> newParameters)
{
var newQueryStringNameValueCollection = new NameValueCollection(HttpContext.Current.Request.QueryString);
foreach (var parameter in newParameters)
{
newQueryStringNameValueCollection[parameter.Key] = parameter.Value;
}
return ToQueryString(newQueryStringNameValueCollection);
}
Your view would call the function by doing an inline initialization of a dictionary and passing it to the helper function like this:
<a href='/SomeController/SomeAction<%=Html.GetCurrentQueryStringWithReplacements(new Dictionary<string,string>() {
{ QuerystringHandler.Instance.KnownQueryParameters[QuaryParameters.PageNr], "2" },
{ QuerystringHandler.Instance.KnownQueryParameters[QuaryParameters.AnotherParam], "1234" }})%>'>Some Link</a>
I did JUST what you need!
I created an HTML helper for this. The helper takes the same parameters as a normal helper. Yet, it keeps the current values from the URL. I made it both for the URL helper as a ActionLink helper.
This replaces the: Url.Action()
<a href='<%= Html.UrlwParams("TeamStart","Inschrijvingen", new {modID=item.Mod_ID}) %>' title="Selecteer">
<img src="<%= Url.Content("~/img/arrow_right.png") %>" alt="Selecteer" width="16" /></a>
and this replaces the Html.ActionLink()
<%: Html.ActionLinkwParams("Tekst of url", "Action", new {test="yes"}) %>
Here is the helper:
using System;
using System.Web.Mvc;
using System.Web.Routing;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Web.Mvc.Html;
namespace MVC2_NASTEST.Helpers {
public static class ActionLinkwParamsExtensions {
public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, string controller, object extraRVs, object htmlAttributes) {
NameValueCollection c = helper.ViewContext.RequestContext.HttpContext.Request.QueryString;
RouteValueDictionary r = new RouteValueDictionary();
foreach (string s in c.AllKeys) {
r.Add(s, c[s]);
}
RouteValueDictionary htmlAtts = new RouteValueDictionary(htmlAttributes);
RouteValueDictionary extra = new RouteValueDictionary(extraRVs);
RouteValueDictionary m = RouteValues.MergeRouteValues(r, extra);
//return System.Web.Mvc.Html.LinkExtensions.ActionLink(helper, linktext, action, controller, m, htmlAtts);
return helper.ActionLink(linktext, action, controller, m, htmlAtts);
}
public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action) {
return ActionLinkwParams(helper, linktext, action, null, null, null);
}
public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, string controller) {
return ActionLinkwParams(helper, linktext, action, controller, null, null);
}
public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, object extraRVs) {
return ActionLinkwParams(helper, linktext, action, null, extraRVs, null);
}
public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, string controller, object extraRVs) {
return ActionLinkwParams(helper, linktext, action, controller, extraRVs, null);
}
public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, object extraRVs, object htmlAttributes) {
return ActionLinkwParams(helper, linktext, action, null, extraRVs, htmlAttributes);
}
}
public static class UrlwParamsExtensions {
public static string UrlwParams(this HtmlHelper helper, string action, string controller, object extraRVs) {
NameValueCollection c = helper.ViewContext.RequestContext.HttpContext.Request.QueryString;
RouteValueDictionary r = RouteValues.optionalParamters(c);
RouteValueDictionary extra = new RouteValueDictionary(extraRVs);
RouteValueDictionary m = RouteValues.MergeRouteValues(r, extra);
string s = UrlHelper.GenerateUrl("", action, controller, m, helper.RouteCollection, helper.ViewContext.RequestContext, false);
return s;
}
public static string UrlwParams(this HtmlHelper helper, string action) {
return UrlwParams(helper, action, null, null);
}
public static string UrlwParams(this HtmlHelper helper, string action, string controller) {
return UrlwParams(helper, action, controller, null);
}
public static string UrlwParams(this HtmlHelper helper, string action, object extraRVs) {
return UrlwParams(helper, action, null, extraRVs);
}
}
}
How does it work?
The calls are the same as for the Html.ActionLink() so you simple can replace those.
The method does the following:
It takes all the optional parameters from the current URL and places them in a RouteValueDictionary.
It also places the htmlattributes in a dictionary.
Then it takes the extra routevalues which you specified manually and places those in a RouteValueDictionary as well.
The key then is to merge the ones from the URL and the ones manually specified.
This happens in the RouteValues class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;
using System.Collections.Specialized;
using System.Web.Mvc;
namespace MVC2_NASTEST {
public static class RouteValues {
public static RouteValueDictionary optionalParamters() {
return optionalParamters(HttpContext.Current.Request.QueryString);
}
public static RouteValueDictionary optionalParamters(NameValueCollection c) {
RouteValueDictionary r = new RouteValueDictionary();
foreach (string s in c.AllKeys) {
r.Add(s, c[s]);
}
return r;
}
public static RouteValueDictionary MergeRouteValues(this RouteValueDictionary original, RouteValueDictionary newVals) {
// Create a new dictionary containing implicit and auto-generated values
RouteValueDictionary merged = new RouteValueDictionary(original);
foreach (var f in newVals) {
if (merged.ContainsKey(f.Key)) {
merged[f.Key] = f.Value;
} else {
merged.Add(f.Key, f.Value);
}
}
return merged;
}
public static RouteValueDictionary MergeRouteValues(this RouteValueDictionary original, object newVals) {
return MergeRouteValues(original, new RouteValueDictionary(newVals));
}
}
}
This is all pretty straightforward. In the end, the actionlink is made with the merged routevalues. This code also lets you remove values from the URL.
Examples:
Your URL is localhost.com/controller/action?id=10&foo=bar. If in that page you place this code
<%: Html.ActionLinkwParams("Tekst of url", "Action", new {test="yes"}) %>
the URL returned in that element will be localhost.com/controller/action?id=10&foo=bar&test=yes.
If you want to remove an item, you just set the item as an empty string. For example,
<%: Html.ActionLinkwParams("Tekst of url", "Action", new {test="yes", foo=""}) %>
will return the URL in the <a> element: localhost.com/controller/action?id=10&test=yes
I'm guessing this is all you need?
If you have some additional questions, just ask.
Extra:
Sometimes you will want to keep your values inside your action too, when you will redirect to another Action. With my RouteValues class, this can be done very easily:
public ActionResult Action(string something, int? somethingelse) {
return RedirectToAction("index", routeValues.optionalParamters(Request.QueryString));
}
If you still want to add some optional parameters, no problem!
public ActionResult Action(string something, int? somethingelse) {
return RedirectToAction("index", routeValues.optionalParamters(Request.QueryString).MergeRouteValues(new{somethingelse=somethingelse}));
}
I think that covers pretty much everything you'll need.
If you want to set the query string in a link of a view:
Html.ActionLink("LinkName", "Action", "Controller", new { param1 = value1, param2 = value2 }, ...)
If you want to set it in the browser URL after a post back, just call Route* in an action like RouteToAction() and set the parameter key/value you want.
If you use the action public ActionResult List(MyStrongType data), you need to include all page settings (page index, ordering,...) as parameters to 'MyStrongType', and the data object will contain all infomation for the view.
In the view, if you need to generate a URL, using the approach of CallMeLaNN:
Html.ActionLink("LinkName", "Action", "Controller", new { param1 = Model.value1, param2 = Model.param2, ... });. You need to manually set all the parameters here or create a helper to help you fill the URL.
You don't need to care about current parameters included in the address.
You can route:
routes.MapRoute(
"custome",
"{controller}/{action}/",
new { controller = "Home", action = "Index"}
);
to generate all the parameters as a query string.

In MVC3 Razor, how do I get the html of a rendered view inside an action?

Does anybody know how to get the generated html of a view inside an action?
Is it something like this:
public ActionResult Do()
{
var html = RenderView("hello", model);
...
}
I use a static method in a class I called Utilities.Common I pass views back to the client as properties of JSON objects constantly so I had a need to render them to a string. Here ya go:
public static string RenderPartialViewToString(Controller controller, string viewName, object model)
{
controller.ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
ViewContext viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.ToString();
}
}
This will work for full views as well as partial views, just change ViewEngines.Engines.FindPartialView to ViewEngines.Engines.FindView.
The accepted answer by #Chev above is good, but I wanted to render the result of a specific action, not just a particular view.
Also, I needed to be able to pass parameters to that action rather than rely on injecting a model.
So I came up with my own method, that I put in the base class of my controllers (making it available to them all):
protected string RenderViewResultAsString(ViewResult viewResult)
{
using (var stringWriter = new StringWriter())
{
this.RenderViewResult(viewResult, stringWriter);
return stringWriter.ToString();
}
}
protected void RenderViewResult(ViewResult viewResult, TextWriter textWriter)
{
var viewEngineResult = this.ViewEngineCollection.FindView(
this.ControllerContext,
viewResult.ViewName,
viewResult.MasterName);
var view = viewEngineResult.View;
try
{
var viewContext = new ViewContext(
this.ControllerContext,
view,
this.ViewData,
this.TempData,
textWriter);
view.Render(viewContext, textWriter);
}
finally
{
viewEngineResult.ViewEngine.ReleaseView(this.ControllerContext, view);
}
}
Suppose I have an action called Foo that takes a model object and some other parameters, which together influence what view will be used:
public ViewResult Foo(MyModel model, int bar)
{
if (bar == 1)
return this.View("Bar1");
else
return this.View("Bar2", model);
}
Now, if I want to get the result of calling action Foo, I can simply get the ViewResult by invoking the Foo method, and then call RenderViewResultAsString to get the HTML text:
var viewResult = this.Foo(model, bar);
var html = this.RenderViewResultAsString(viewResult);

Using HtmlHelper in a Controller

Is it possible to use HtmlHelper in a controller, for-example to get the TextBox(...) method? not that I can't write the html that it generates myself, but I just want to understand how this works so I can create the best solution.
Here's an example adapted from this:
var h = new HtmlHelper(new ViewContext(ControllerContext, new WebFormView("omg"), new ViewDataDictionary(), new TempDataDictionary()), new ViewPage());
h.TextBox("myname");
Note that this is a hack, it can be done but I don't think there's any good reason to do this...
You can use method like this:
public static HtmlHelper GetHtmlHelper(this Controller controller)
{
var viewContext = new ViewContext(controller.ControllerContext, new FakeView(), controller.ViewData, controller.TempData, TextWriter.Null);
return new HtmlHelper(viewContext, new ViewPage());
}
public class FakeView : IView
{
public void Render(ViewContext viewContext, TextWriter writer)
{
throw new InvalidOperationException();
}
}
The HtmlHelper is part of the View mechanism by design and should be considered separate to the Controller and Model parts of MVC. I am not sure why you would want to generate controls inside the controller as it's role is to deliver the Data to the view for rendering.
I am not saying that you cannot achieve it, but for good design it would be better.
Can you explain what you are trying to achieve and then we could look at doing it in an "MVC way"?
using System.Web.Mvc;
using System.Web.Mvc.Html;
var h = new HtmlHelper<Effort>(new ViewContext(ControllerContext, new WebFormView(ControllerContext, "omg"), new ViewDataDictionary(), new TempDataDictionary(), new StringWriter()), new ViewPage());
h.DisplayFor(e => Model.Efforts[i].Content.Offer.Price1.Value)
For .NET Core 2 MVC: https://github.com/aspnet/Mvc/issues/7321
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.Options;
using System.IO;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
public class HelperGenerator
{
private readonly IHtmlGenerator _htmlGenerator;
private readonly ICompositeViewEngine _compositeViewEngine;
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly IViewBufferScope _viewBufferScope;
private readonly IActionContextAccessor _actionContextAccessor;
private readonly HtmlHelperOptions _htmlHelperOptions;
public HelperGenerator(IHtmlGenerator htmlGenerator, ICompositeViewEngine compositeViewEngine, IModelMetadataProvider modelMetadataProvider, IViewBufferScope viewBufferScope, IActionContextAccessor actionContextAccessor, IOptions<MvcViewOptions> options)
{
_htmlGenerator = htmlGenerator;
_compositeViewEngine = compositeViewEngine;
_modelMetadataProvider = modelMetadataProvider;
_viewBufferScope = viewBufferScope;
_actionContextAccessor = actionContextAccessor;
_htmlHelperOptions = options.Value.HtmlHelperOptions;
}
public IHtmlHelper HtmlHelper(ViewDataDictionary ViewData, ITempDataDictionary TempData)
{
var helper = new HtmlHelper(_htmlGenerator, _compositeViewEngine, _modelMetadataProvider, _viewBufferScope, HtmlEncoder.Default, UrlEncoder.Default);
var viewContext = new ViewContext(_actionContextAccessor.ActionContext,
new FakeView(),
ViewData,
TempData,
TextWriter.Null,
_htmlHelperOptions);
helper.Contextualize(viewContext);
return helper;
}
private class FakeView : IView
{
public string Path => "View";
public Task RenderAsync(ViewContext context)
{
return Task.FromResult(0);
}
}
}
Make sure to register in services:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
If someone is trying to do this from without a Controller (like when unit testing), there are further problems to deal with, as many of these methods (which I know, were not a testing scenario, but for that scenario) were throwing Null exceptions (ViewContext.ScopeCache). You can see this by the following (note all of these ways require a ViewContext instance to be formed, which is one of the parameters you insert into the constructor of the HtmlHelper instance, so on that object):
viewContext.UnobtrusiveJavaScriptEnabled = false;
Simply setting that value throws an exception with many of these methods, but the problem was fixed for me by this answer, see how he gets an HtmlHelper (see also here).
using System.Web.Mvc;
using System.Web.Mvc.Html;
HtmlHelper helper = new HtmlHelper(new ViewContext(ControllerContext, new WebFormView(ControllerContext, "Index"), new ViewDataDictionary(), new TempDataDictionary(), new System.IO.StringWriter()), new ViewPage());

How do I access HtmlHelper methods from within MY OWN HtmlHelper?

I am writing my own HtmlHelper extenstion for ASP.NET MVC:
public static string CreateDialogLink (this HtmlHelper htmlHelper, string linkText,
string contentPath)
{
// fix up content path if the user supplied a path beginning with '~'
contentPath = Url.Content(contentPath); // doesn't work (see below for why)
// create the link and return it
// .....
};
Where I am having trouble is tryin to access UrlHelper from within my HtmlHelper's definition. The problem is that the way you normally access HtmlHelper (via Html.MethodName(...) ) is via a property on the View. This isn't available to me obviously from with my own extension class.
This is the actual MVC source code for ViewMasterPage (as of Beta) - which defines Html and Url.
public class ViewMasterPage : MasterPage
{
public ViewMasterPage();
public AjaxHelper Ajax { get; }
public HtmlHelper Html { get; }
public object Model { get; }
public TempDataDictionary TempData { get; }
public UrlHelper Url { get; }
public ViewContext ViewContext { get; }
public ViewDataDictionary ViewData { get; }
public HtmlTextWriter Writer { get; }
}
I want to be able to access these properties inside an HtmlHelper.
The best I've come up with is this (insert at beginning of CreateDialogLink method)
HtmlHelper Html = new HtmlHelper(htmlHelper.ViewContext, htmlHelper.ViewDataContainer);
UrlHelper Url = new UrlHelper(htmlHelper.ViewContext.RequestContext);
Am I missing some other way to access the existing HtmlHelper and UrlHelper instances - or do i really need to create a new one? I'm sure there isn't much overhead but I'd prefer to use the preexisting ones if I can.
Before asking this question I had looked at some of the MVC source code, but evidently I missed this, which is how they do it for the Image helper.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "The return value is not a regular URL since it may contain ~/ ASP.NET-specific characters")]
public static string Image(this HtmlHelper helper, string imageRelativeUrl, string alt, IDictionary<string, object> htmlAttributes) {
if (String.IsNullOrEmpty(imageRelativeUrl)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "imageRelativeUrl");
}
UrlHelper url = new UrlHelper(helper.ViewContext);
string imageUrl = url.Content(imageRelativeUrl);
return Image(imageUrl, alt, htmlAttributes).ToString(TagRenderMode.SelfClosing);
}
Looks like instantiating a new UrlHelper is the correct approach after all. Thats good enough for me.
Update: RTM code from ASP.NET MVC v1.0 Source Code is slightly different as pointed out in the comments.
File: MVC\src\MvcFutures\Mvc\ImageExtensions.cs
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "The return value is not a regular URL since it may contain ~/ ASP.NET-specific characters")]
public static string Image(this HtmlHelper helper, string imageRelativeUrl, string alt, IDictionary<string, object> htmlAttributes) {
if (String.IsNullOrEmpty(imageRelativeUrl)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "imageRelativeUrl");
}
UrlHelper url = new UrlHelper(helper.ViewContext.RequestContext);
string imageUrl = url.Content(imageRelativeUrl);
return Image(imageUrl, alt, htmlAttributes).ToString(TagRenderMode.SelfClosing);
}
I faced a similar issue and decided that it would be easier to just call the UrlHelper in the view and pass the output to my HtmlHelper extension. In your case it would look like:
<%= Html.CreateDialogLink( "text", Url.Content( "~/...path.to.content" ) ) %>
If you want to access the extension methods on the existing HtmlHelper that is passed into your class, you should only need to import System.Web.Mvc.Html in your source code file and you will get access to them (that's where the extension classes are defined). If you want a UrlHelper, you'll need to instantiate that as the HtmlHelper you are getting doesn't have a handle for the ViewPage that it's coming from.
If you need to create a UrlHelper in a utility class you can do the following :
string url = "~/content/images/foo.jpg";
var urlHelper = new UrlHelper(new RequestContext(
new HttpContextWrapper(HttpContext.Current),
new RouteData()), RouteTable.Routes);
string absoluteUrl = urlHelper.Content(url);
This allows you to use routing or '~ expansion' away from an MVC context.
Well, you can always pass the instance of the page to the extension method. I think that is a much better way of doing this than creating new instances in your method.
You could also define this method on a class that derives from MasterPage/ViewMasterPage and then derive the page from that. This way, you have access to all the properties of the instance and don't have to pass them around.

Resources