Use a disposable helper .net mvc backend - c#

I have an IDisposable HTML helper to create a specific html structure that I need very often in my app. I use it with razor and its works perfectly
#using (Html.SlidePanel("settings-button"))
{
<span>panel content</span>
}
I have a html helper component based on the structure and I want to use my SlidePanel inside.
public class MyComponent : IHtmlString
{
private readonly HtmlHelper html;
public MyComponent(HtmlHelper html)
{
this.html = html;
}
public override string ToString()
{
return Render();
}
public string ToHtmlString()
{
return ToString();
}
private string Render()
{
// I want to use my SlidePanel at this place
string renderHtml = "<div>component html</div>";
return renderHtml;
}
}
public static class MyComponentHtmlHelper
{
public static MyComponent MyComponent(this HtmlHelper html)
{
return new MyComponent(html);
}
}
How can I achieve this ?
Thanks

You'll need to intercept the string that SlidePanel would normally be sending to the general output. Something like this should work:
var originalWriter = html.ViewContext.Writer;
using (var stringWriter = new StringWriter())
{
html.ViewContext.Writer = stringWriter;
using (html.SlidePanel())
{
stringWriter.Write("<div>component html</div>");
}
html.ViewContext.Writer = originalWriter;
return stringWriter.ToString();
}

So here you go, example HERE
public static class DisposableExtensions
{
public static IDisposable DisposableDiv(this HtmlHelper htmlHelper)
{
return new DisposableHelper(
() => htmlHelper.BeginDiv(),
() => htmlHelper.EndDiv());
}
public static void BeginDiv(this HtmlHelper htmlHelper)
{
htmlHelper.ViewContext.Writer.Write("<div>");
}
public static void EndDiv(this HtmlHelper htmlHelper)
{
htmlHelper.ViewContext.Writer.Write("</div>");
}
}
}
As you can see in example "Hello Stranger" placed in additional div

I find a solution thanks to Baximilian and StriplingWarrior answers
Yes, I need to intercept the string that SlidePanel would normally be sending to the general output. So I can't use the ViewContext.Writer.
And yes, I need to send methods a parameters to my SlidePanel constructor.
So the solution is to add a second constructor to SlidePanel class :
public string HtmlString;
public SlidePanel(Func<string> begin, Func<string> content, Func<string> end)
{
this.HtmlString = string.Format("{0}{1}{2}", begin(), content(), end());
}
And add public helpers inside the SlidePanel Component class
public static string BeginSlidePanel(this HtmlHelper html)
{
return BeginHtml();
}
public static string ContentSlidePanel(this HtmlHelper html, string htmlString)
{
return htmlString;
}
public static string EndSlidePanel(this HtmlHelper html)
{
return EndHtml();
}
And then, I can use my SlidePanel like this :
new SlidePanel(
() => html.BeginSlidePanel(),
() => html.ContentSlidePanel(GetMyHtmlContent()),
() => html.EndSlidePanel()).HtmlString
Thanks a lot !

Related

ASP.NET Core Html Helper rendering raw text instead of formatted HTML using TagBuilder

I'm working on an Html Helper to create a control that will consist of multiple elements, build using TagBuilders. The control itself will be rendered using a TagBuilder that contains a div with all child elements.
Per this: https://developercommunity.visualstudio.com/content/problem/17287/tagbuilder-tostring-returns-the-type-of-tagbuilder.html
I implemented a Render() method to create the control and return it as a string:
public class MyCustomControl
{
public override string ToString()
{
return Render();
}
private string Render()
{
TagBuilder mainContainer = new TagBuilder("div");
// Generate child elements and append to mainContainer...
using (StringWriter writer = new StringWriter())
{
mainContainer.WriteTo(writer, HtmlEncoder.Default);
return writer.ToString();
}
}
}
And made an extension method to call it in a Razor View:
public static MyCustomControl(this IHtmlHelper html)
{
return new MyCustomControl();
}
And include it in Views like this:
#(Html.MyCustomControl()
)
The problem is instead of being rendered html, I get raw html text output to the View, so I actually see:
<div><!-- all child controls html here --></div>
Instead of there being an element there.
You need to return an instance of IHtmlContent instead of string :
public static class HtmlHelperExtension {
public static IHtmlContent MyCustomControl(this IHtmlHelper html)
{
var result = new MyCustomControl();
return html.Raw(result.Render());
}
}
Test Case :
public class MyCustomControl
{
public override string ToString()
{
return Render();
}
public string Render()
{
TagBuilder mainContainer = new TagBuilder("div");
mainContainer.Attributes.Add(new KeyValuePair<string, string>("data-id","123") );
// Generate child elements and append to mainContainer...
using (StringWriter writer = new StringWriter())
{
mainContainer.WriteTo(writer, HtmlEncoder.Default);
var result=writer.ToString();
return result;
}
}
the result will be :
<div data-id="123"></div>
I believe a better way to do this is to implement IHtmlContent in your custom control, like so:
public class MyCustomControl : IHtmlContent
{
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
{
TagBuilder mainContainer = new TagBuilder("div");
// Generate child elements and append to mainContainer...
mainContainer.WriteTo(writer, encoder);
}
}
then your HtmlHelper extension doesn't change all that much:
public static IHtmlContent MyCustomControl(this IHtmlHelper html)
{
return new MyCustomControl();
}
This should eliminate the temporary strings which are created in the accepted answer.

How can i override Url.Action

Now i use to override extensions like :
public abstract class MyWebViewPage<T> : WebViewPage<T>
{
public new MyHtmlHelper<T> Html { get; set; }
public override void InitHelpers()
{
Ajax = new AjaxHelper<T>(ViewContext, this);
Url = new UrlHelper(ViewContext.RequestContext);
Html = new MyHtmlHelper<T>(ViewContext, this);
}
}
public class MyHtmlHelper<T> : HtmlHelper<T>
{
public MyHtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer) :
base(viewContext, viewDataContainer)
{
}
public MvcHtmlString ActionLink(string linkText, string actionName)
{
return ActionLink(linkText, actionName, null, new RouteValueDictionary(), new RouteValueDictionary());
}
}
How to add Url.Action helper here with all overloaded version?
UPD: I should override all standard method because many peoples work on this and i they should use standard helpers but with my functionality
You don't need to override neither the Url.Action helper nor the HtmlHelper actions. You can create Extension Methods instead. Here's an example:
public static class MyHelpers
{
public static string MyAction(this UrlHelper url, string actionName)
{
// return whatever you want (here's an example)...
return url.Action(actionName, new RouteValueDictionary());
}
}
Then, you can use the method in your views like this:
#Url.MyAction("MyActionName")
UPDATE:
I wouldn't recommend overriding the Url.Action method. Creating extension methods is much easier and cleaner. But, here's how you can do it:
public class MyUrlHelper : UrlHelper
{
public override string Action(string actionName)
{
return base.Action(actionName, new RouteValueDictionary());
}
}

How to "send" a method to inside another method?

I have the following:
public class Mail {
public String Obfuscate(String email) {
return email.Replace("#", "at").Replace(".", "dot");
}
}
I am calling the method Obfuscate in a class, as follows:
public class Resolver {
public Data GetData () {
return new Data { Email = new Mail().Obfuscate(myEmail) };
}
public String Translate(string value) { /* Some Code */ }
}
The problem is that Obfuscate does the replacement in English: # > at, . > dot
But in the Resolver class the method Translate does exactly what I need ...
How can I "pass" the Translate method to the Obfuscate method so this one uses it to translate # and . to at and dot in the current language?
So the code line inside Obfuscate:
return email.Replace("#", "at").Replace(".", "dot");
Would be become:
return email.Replace("#", Translate("#")).Replace(".", Translate("."));
Where Translate would be the method that I am "passing" to it.
Than You,
Miguel
Consider a different design:
public interface ITranslator
{
string Translate(string s);
}
public class Obfuscator
{
public Obfuscator(ITranslator translator)
{
this.translator = translator;
}
public string Obfuscate(string email)
{
var at = translator.Translate("at");
var dot = translator.Translate("dot");
return email.Replace("#", at).Replace(".", dot);
}
private ITranslator translator;
}
public class EnglishTranslator : ITranslator
{
public string Translate(string s)
{
return s;
}
}
public class PolishTranslator : ITranslator
{
public PolishTranslator() //or `FileInfo dictionaryFile` parameter perhaps
{
// for simplicity
translations = new Dictionary<string, string>();
translations.Add("at", "malpa");
translations.Add("dot", "kropka");
}
public string Translate(string s)
{
return translations[s];
}
private Dictionary<string, string> translations;
}
However you really should consider using a ResourceManager. Resource related mechanisms are designed to deal with translations.
I think #BartoszKP's answer is the right design decision. For completeness, here's how to do what you asked.
Change Mail to take a Func<string,string>:
public class Mail {
public String Obfuscate(String email, Func<string,string> translate) {
return email.Replace("#", translate("at")).Replace(".", translate("dot"));
}
}
And pass your Translate method to it:
public class Resolver {
public Data GetData () {
return new Data { Email = new Mail().Obfuscate(myEmail, Translate) };
}
public String Translate(string value) { /* Some Code */ }
}

auto closing custom helper

I am trying to implement auto closing custom helper.
I have found how to to this below:
Custom html helpers: Create helper with "using" statement support
I have done everything except:
what i am supossed to return here?
public static Action BeginField(this HtmlHelper htmlHelper, string formName)
{
string result = string.Format("<div class=\"FullWidthForm\"><span>{0}</span>", formName);
result += "<ul class=\"Left\">";
htmlHelper.ViewContext.HttpContext.Response.Write(result);
return ???????
}
I am asking because i have error as follows:
Error 9 'FieldContainer.BeginField(System.Web.Mvc.HtmlHelper,
string)': not all code paths return a
value
so i do not have idea what to return
Ok I have created everything how ever:
public static class DisposableExtensions
{
public static IDisposable DisposableField(this HtmlHelper htmlHelper, string formName)
{
//'HtmlHelpers.DisposableHelper' does not contain a constructor that takes 2 arguments
return new DisposableHelper(
() => htmlHelper.BeginField(formName),
() => htmlHelper.EndField());
}
public static void BeginField(this HtmlHelper htmlHelper, string formName)
{
htmlHelper.ViewContext.HttpContext.Response.Write("<ul><li>" + formName + "</li>");
}
public static void EndField(this HtmlHelper htmlHelper)
{
htmlHelper.ViewContext.HttpContext.Response.Write("</ul>");
}
}
class DisposableHelper : IDisposable
{
private Action end;
// When the object is create, write "begin" function
// make this public so it can be accessible
public DisposableHelper(Action begin, Action end)
{
this.end = end;
begin();
}
// When the object is disposed (end of using block), write "end" function
public void Dispose()
{
end();
}
}
Your BeginField does not need to return Action.
Action is a delegate, a callback you need to pass to the DisposableHelper constructor. You will setup it as () -> htmlHelper.BeginField(formName). DisposableHelper works by remembering the two callbacks you pass in - first to start the tag (BeginField) which is called immediately, second to end the tag (EndField) which is called on disposal of the DisposableHelper.
UPDATE: This is how you should implement it.
a) Copy the DisposableHelper class.
b) Write an extension to DisposableExtensions:
public static class DisposableExtensions
{
public static IDisposable DisposableField(this HtmlHelper htmlHelper, string formName)
{
return new DisposableHelper(
() => htmlHelper.BeginField(formName),
() => htmlHelper.EndField()
);
}
}
c) Change your BeginField declaration to return void:
public static void BeginField(...)
d) Add EndField method to close the tag.
e) Use it this way:
using (Html.DisposableField("MyForm"))
{
...
}

MVC Mock HttpContextBase which is used in a helper

I am using a helper in my controllers and in my views that I have found somewhere on the internet. The helper is called like this in my controller "Url.SiteRoot();"
How can I get my controller to not throw an Exception whenever the helper is called? I am using MVCContrib and moq for my unit tests.
I am thinking of implementing some kind of a check in the helper but it feel like the MVCContrib framework or the moq should be able to handle this so that I don't need to add Exception code in my helpers just to be able to pass the unit tests.
You can see the Helper code here:-
namespace System.Web.Mvc {
public static class UrlHelpers {
public static string SiteRoot(HttpContextBase context) {
return SiteRoot(context, true);
}
public static string SiteRoot(HttpContextBase context, bool usePort) {
var Port = context.Request.ServerVariables["SERVER_PORT"];
if (usePort) {
if (Port == null || Port == "80" || Port == "443")
Port = "";
else
Port = ":" + Port;
}
var Protocol = context.Request.ServerVariables["SERVER_PORT_SECURE"];
if (Protocol == null || Protocol == "0")
Protocol = "http://";
else
Protocol = "https://";
var appPath = context.Request.ApplicationPath;
if (appPath == "/")
appPath = "";
var sOut = Protocol + context.Request.ServerVariables["SERVER_NAME"] + Port + appPath;
return sOut;
}
public static string SiteRoot(this UrlHelper url) {
return SiteRoot(url.RequestContext.HttpContext);
}
public static string SiteRoot(this ViewPage pg) {
return SiteRoot(pg.ViewContext.HttpContext);
}
public static string SiteRoot(this ViewUserControl pg) {
var vpage = pg.Page as ViewPage;
return SiteRoot(vpage.ViewContext.HttpContext);
}
public static string SiteRoot(this ViewMasterPage pg) {
return SiteRoot(pg.ViewContext.HttpContext);
}
public static string GetReturnUrl(HttpContextBase context) {
var returnUrl = "";
if (context.Request.QueryString["ReturnUrl"] != null) {
returnUrl = context.Request.QueryString["ReturnUrl"];
}
return returnUrl;
}
public static string GetReturnUrl(this UrlHelper helper) {
return GetReturnUrl(helper.RequestContext.HttpContext);
}
public static string GetReturnUrl(this ViewPage pg) {
return GetReturnUrl(pg.ViewContext.HttpContext);
}
public static string GetReturnUrl(this ViewMasterPage pg) {
return GetReturnUrl(pg.Page as ViewPage);
}
public static string GetReturnUrl(this ViewUserControl pg) {
return GetReturnUrl(pg.Page as ViewPage);
}
}
}
As #Jeremy Frey writes you're getting the exceptions because you're failing to stub/fake some essential parts of the HttpContext.
How about using:
Request.Url.GetLeftPart(System.UriPartial.Authority)
instead of trying to build the logic for building the url yourself? If I remember correctly it should pick up the protocol and port correctly, as well as any virtual directory, site, etc.
You've probably realized that the reason you're getting exceptions from your extension methods is due to unimplemented properties or methods on the mocked objects, e.g. Request on HttpContextBase, or RequestContext on UrlHelper.
Take a look at some of the strategies posted here for ideas on how to mock the extension method calls. I personally prefer this strategy, which would have you refactoring your extension methods to bet swappable at runtime.
For example, instead of:
public static class UrlHelperExtensions
{
public static string GetReturnUrl(this UrlHelper helper)
{
return // your implementation of GetReturnUrl here
}
}
You'd have:
public interface IUrlHelperExtensions
{
string GetReturnUrl(UrlHelper helper);
}
public static class UrlHelperExtensions
{
public static IUrlHelperExtensions Extensions(this UrlHelper target)
{
return UrlHelperExtensionFactory(target);
}
static UrlExtensions
{
UrlHelperExtensionFactory = () => new DefaultUrlHelperExtensionStrategy();
}
public static Func UrlHelperExtensionFactory { get; set; }
}
public DefaultUrlHelperExtensionStrategy : IUrlHelperExtensions
{
public string GetReturnUrl(UrlHelper helper)
{
return // your implementation of GetReturnUrl here
}
}
You'd need to change the way you'd call your extension methods, from urlHelper.GetReturnUrl() to urlHelper.Extensions().GetReturnUrl(), and during unit testing, you can set UrlHelperExtensions.UrlHelperExtensionFactory to a mocked object, but this way, you can control the extension methods' behavior at test time.
That code looks a little complicated. I think this does the same thing and would be much easier to test. (I'm not sure why it needs to be though.)
public string FullApplicationPath(HttpRequestBase request)
{
var path = request.Url.AbsoluteUri.Replace(request.Url.AbsolutePath,string.Empty);
if (!string.IsNullOrEmpty(request.Url.Query))
{
path = path.Replace(request.Url.Query, string.Empty);
}
return path + request.ApplicationPath;
}

Categories