Render partial in an extension method fails - c#

I'm creating a tabcontainer that shows information from partials. The code that i've created is as follows:
//Entering extension method, m_helper is of type HtmlHelper
foreach (var tab in m_tabList)
{
sb.AppendLine("<div class='tabContent'>");
m_helper.RenderPartial(tab.PartialName);
sb.AppendLine("</div>");
}
//Returning sb.ToString to the caller method
This will not work because the renderpartial writes directly to the output stream. I cannot render the partial to a string either. to add it to the stringbuilder object.
Any suggestions?

use
m_helper.Partial(tab.PartialName);
This will return MvcHtmlString.

Related

How to create server method, that populates collection and returns partial view, in ASP.NET MVC?

I am trying to call IEnumerable method in my _Layout.cshtml file. At the final I was adviced to "use html.action - to call server method that populates collection and returns partial view".
Currently I have created partial file _Dodatki.cshtml, that contains call of IEnumerable method (Aktualnosci.cs is model file):
#model IEnumerable<DluzynaSzkola.Models.Aktualnosci>
In my _Layout.cshtml I called method from my constructor with:
#Html.Action("_Dodatki", "AktualnosciController ", new {area="" })
And at the final I want to create method in my AktualnosciConstructor.cs file. Currenly I have method:
[ChildActionOnly]
[ActionName("_Dodatki")]
public ActionResult Dodatki()
{
IList<Aktualnosci> lista = new IList<Aktualnosci>();
return PartialView("_Dodatki", lista);
}
Unfortunately, when using syntax as above, it gives me message in compiler:
"cannot create an instance of the abstract class or interface
'IList'".
When replacing 'IList' with 'List', it gives me exception:
"System.Web.HttpException: The controller for path '/' was not found
or does not implement IController."
I have no idea how in other way I can populate collection in the method.
edit: As per request, below AktualnosciController.cs definition, with no other methods:
namespace DluzynaSzkola.Controllers
{
public class AktualnosciController : Controller
{
//here are other methods
[ChildActionOnly]
[ActionName("_Dodatki")]
public ActionResult Dodatki()
{
IList<Aktualnosci> lista = new IList<Aktualnosci>();
return PartialView("_Dodatki", lista);
}
}
}
as noticed by GTown-Coder your controller name seems wrong. Updated my answer accordingly.
I think that your problem might be the same as answered by this SO post.
try specifying the Area name and, if this controller is not in an area simply add an empty area name.
#Html.Action("_Dodatki", "AktualnosciController ", new {area="" })
Even if this does not solve your problem it is good practice because if this view is latter used within an area it will try to find the controller in the area and not in the root space.
Allright, I have implemented changes to my project, that works fine.
My in _Layout.cshtml call is changed a bit. AktualnosciController supposed to be called just Aktualnosci !!!
<div class="kontenerDodatkiLayout hidden-xs hidden-sm">
<div class="archiwum">Archiwum</div>
#Html.Action("_Dodatki", "Aktualnosci", new { area = "" })
</div>
My partial view _Dodatki.cshtml model call is changed a bit:
#model IEnumerable<DateTime>
<div class="wpisDodatki">
#foreach (var item in Model)
{
<div> #Html.DisplayFor(modelItem => item)</div>
}
<p>test<br />test<br />test</p>
</div>
And method in my controller AktualnosciController.cs looks like that:
//[ChildActionOnly]
[ActionName("_Dodatki")]
public ActionResult Dodatki()
{
using (var context = new DluzynaContext())
{
var lista = context.Indeks.Select(it => it.Dzien).ToList();
return PartialView("_Dodatki", lista);
}
}
in here lista is passed to my partial view _Dodatki, and it is populated with context property Indeks and model property Dzien.
Thanks guys for your help #Wndrr , #GTown-Coder.

Set TextBox value from ViewBag list in MVC5 C#

How can I set TextBox value in MVC5 from ViewBag which contains a list? As you can see my list is in Viewbag.photos and I want to have each value of photo.id in my TextBox and then pass it to controller
#foreach (var photo in ViewBag.photos)
{
#if (#photo.comment != null)
{
<h6>#photo.comment</h6>
}
else
{
<h6> - </h6>
}
#Html.TextBox("photoID", #photo.id)
}
Trying to do that I get an error:
Error CS1973 'HtmlHelper>' has no applicable method
named 'TextBox' but appears to have an extension method by that name.
Extension methods cannot be dinamically dispached.
Maybe there's another workaround?
This is happening because ViewBag.photos is a dynamic object. The compiler cannot know its type, so you have to manually cast it to its original type.
For example:
#Html.TextBox("photoID", (int)photo.id)
As a side note (I'm not sure whether this will prevent your code from working, but it's good practice anyway), you also have bit too many #s: to cite Visual Studio, once inside code, you do not need to prefix constructs like "if" with "#". So your final code will look like:
#foreach (var photo in ViewBag.photos)
{
if (photo.comment != null)
{
<h6>#photo.comment</h6>
}
else
{
<h6> - </h6>
}
#Html.TextBox("photoID", (int)photo.id)
}
You should also consider using ViewModels instead of ViewBag to pass data between your controllers and your views.

HtmlHelper to consume html/text inside using

I'm trying to make an HTML helper that will allow for any text inside a using to be consumed and not written to the final page.
The Razor:
<div>
<p>
Before Inline
</p>
#using (Html.IncludeInlineScript("testScript"))
{
<script type="text/javascript">
alert("hello world");
</script>
}
<p>
After Inline
</p>
</div>
The resulting HTML:
<div>
<p>
Before Inline
</p>
<script type="text/javascript">
alert("hello world");
</script>
<!-- Top of Dispose -->
<!-- Bottom of Dispose -->
<p>
After Inline
</p>
</div>
The Helper Extension Method:
public static ScriptWrapper IncludeInlineScript(this HtmlHelper helper, string scriptName)
{
return new ScriptWrapper(helper);
}
The Wrapper:
public class ScriptWrapper : IDisposable
{
private HtmlHelper _helper;
private TextWriter _originalWriter;
private StringBuilder _scriptContents;
public ScriptWrapper(HtmlHelper helper)
{
_helper = helper;
_originalWriter = _helper.ViewContext.Writer;
_scriptContents = new StringBuilder();
_helper.ViewContext.Writer = new StringWriter(_scriptContents);
}
public void Dispose()
{
_originalWriter.WriteLine("<!-- Top of Dispose -->");
_helper.ViewContext.Writer.Flush();
_helper.ViewContext.Writer = _originalWriter;
_originalWriter.WriteLine("<!-- Bottom of Dispose -->");
}
}
The problem here is that despite setting the ViewContext.Writer to a new TextWriter it still is writing to the original writer. Clearly the dispose is being invoked in the right order as the top of the dispose is after the script. While debugging after the new writer has been set the <script> block is not in the stream, however at the time of the dispose the <script>is now contained in the original writer.
Is the razor engine keeping a local copy of the writer and ignores the fact that it had been set to a different instance? This seems like a bug to me.
In case anyone wants a work around, since I'm impatient, I found a way to make this work. It's less than ideal, but it does the trick. If you modify the ScriptWrapper to be:
public class ScriptWrapper : IDisposable
{
private HtmlHelper _helper;
private string _originalString;
private StringBuilder _sb;
public ScriptWrapper(HtmlHelper helper)
{
_helper = helper;
_sb = ((StringWriter) _helper.ViewContext.Writer).GetStringBuilder();
_originalString = _sb.ToString();
_sb.Clear();
}
public void Dispose()
{
var contents = _sb.ToString();
_sb.Clear();
_sb.Append(_originalString);
}
}
You can accomplish the desired result of consuming anything inside the using(...) statement.
I would however still be curious to know if there is a way to do it without hacking a solution like this.
In your original (the question), it's tempting to think that because the markup is within the using block, somehow ScriptWrapper is consuming the markup
<script type="text/javascript">
alert("hello world");
</script>
but as you found out, it is not being consumed by the ScriptWrapper in any way. You are hoping that this markup would get "redirected" somewhere else, to the writer you create, but it isn't. Instead the writer you create is also used to write to the response stream, which is what the ViewContext.Writer is used for, whatever writer it happens to be.
So, rather than having ScriptWrapper consume the markup, Razor is interpreting it as markup like anything else in the view and is, after interpreting it, writing it to the Response stream. From Razor's perspective that markup is intended for the output, not for ScriptWrapper. Just having it enclosed in a using block, does not make the markup any less a part of the overall view.
You answer succeeds because you are not redirecting the markup to somewhere else (which doesn't work), but because you are editing what ends up in the response stream, effectively erasing the markup from the response stream.
I would argue that your solution is not a hack but a correct way of approaching this, given what you want to accomplish.

How to render derived types of a class differently?

I have an Item class. I have around 10-20 derivatives of it each containing different types of data. Now when it comes to rendering different types of Item, I'm forced to use likes of:
<div>
#if (Model is XItem)
{
... rendering logic 1 ...
}
#if (Model is YItem)
{
... rendering logic 2 ...
}
#if (Model is ZItem)
{
... rendering logic 3 ...
}
... goes on and on forever ...
</div>
Unfortunately #Html.DisplayFor() does not work in this case because the Model is type of Item, DisplayTemplates\Item.cshtml is displayed.
HTML helpers don't help either because of the same "if/is" chain.
I could incorporate rendering logic inside the classes themselves, and call #Model.Render() but they belong to business logic, not presentation. It would be a sin.
There is only one option of #Html.Partial(Model.GetType().Name) but it feels wrong. You expect a solution without meta-magic. Is there a better way?
Use Display Templates.
Inside your ~/Views/Shared/DisplayTemplates folder you can add a view with the same name as your type.
When you do #Html.DisplayFor(item) you'll get the view related to that specific type.
UPDATE
I just saw your comment RE DisplayFor so if this doesn't help i'll remove my answer.
I think your approach is fine. You can make it better with an extension method like this:
public static MvcHtmlString GetTypePartial<T>(this HtmlHelper htmlHelper, T model, string viewPrefix = "")
{
string typeName = typeof (T).Name;
string viewName = string.Concat(viewPrefix, typeName);
return htmlHelper.Partial(viewName, model);
}

Why is Orchard auto-encoding strings to the view?

In my driver, I am passing the dynamic object to the view, but it is automatically encoding all my strings which is mangling the links I am constructing in the view.
Here is what I am trying to do:
public class SomeWidgetDriver : ContentPartDriver<SomeWidgetPart>
{
// GET
protected override DriverResult Display(SomeWidgetPart part, string displayType, dynamic shapeHelper)
{
return ContentShape("Parts_SomeWidget",
() => shapeHelper.Parts_SomeWidget(
AppUrl: part.AppUrl,
AppVersion: part.AppVersion,
RenderTo: part.RenderTo,
Test: "xxxx&"));
}
}
When I add the below to the view:
#Model.Test
It renders like this:
xxxx&
Is there a way to stop this from happening? I am trying to get it to exactly render "xxxx&".
I don't think this has to do with Orchard at all.
In Razor everything is html encoded.
Did you try:
#Html.Raw(Model.Test)
?

Categories