MVC 2 - Use HtmlHelper.BeginForm inside method that creates a dynamic form - model-view-controller

I have a method like this:
public MvcHtmlString RenderStuff(HtmlHelper<TModel> htmlHelper)
{
TagBuilder div = new TagBuilder("div");
//Dynamically add elements to form e.g.
div.InnerHtml += System.Web.Mvc.Html.InputExtensions.TextBox(htmlHelper, "MyTextBox").ToHtmlString();
return MvcHtmlString.Create(div.ToString());
}
It all works correctly but I can't work out how I can add a form around it within the method - that is to say that at this point in time I am adding the form in the view like so:
<% using (Html.BeginForm()) { %>
<%=(ViewData["MyStuff"] as MyStuff).RenderStuff(Html)%>
<% } %>
I guess I am trying to do something like this but I am thinking that I will need to somehow render the form to the div and then append the textbox to the form...:
using (htmlHelper.BeginForm())
{
div.InnerHtml += div.InnerHtml += System.Web.Mvc.Html.InputExtensions.TextBox(htmlHelper, "MyTextBox").ToHtmlString();
}
Please don't suggest using TagBuilder("form") and then appending the items to that - I am using the MVC Client Validation and it seems to require using Html.BeginForm - If I can't figure out a way to do this I will leave the form in my view rather than spend time trying to hack the validation to make it work as it is working very nicely at the moment.
Any suggestion greatly appreciated, thanks in advance :-)
Cheers
Rob

I slept on this and had some clarity in the morning here is the answer for anyone who want's to do this in the future at all:
You can't return a string and then render it from the View - return void and render it in the method so that as you are rendering the htmlHelper ViewContext is current/relevant:
public void RenderStuff(HtmlHelper<TModel> htmlHelper)
{
TagBuilder div = new TagBuilder("div");
using(htmlHelper.BeginForm()){
//Dynamically add elements to form e.g.
div.InnerHtml += System.Web.Mvc.Html.InputExtensions.TextBox(htmlHelper, "MyTextBox").ToHtmlString();
htmlHelper.ViewContext.HttpContext.Response.Write(div.ToString());
}
}

Related

How to display a fully formed HTML page as an MVC view?

MVC/ASP.NET/C#/html/javascript newbie question:
I'm trying to move some legacy software into an MVC solution. I have an MVC controller ViewResult method that makes an API call to the legacy system and returns a string which is a fully formed HTML page (including the HTML start and end tags). Some time in the future, I'll rewrite the logic as an MVC view, but for right now I need to just display that page (preferably in a new tab).
I've tried this in the controller:
return View((object)calendar);
(where "calendar" is the string containing the HTML document)
In my view I have
#model string
#{ Layout = null; }
#Model
But that didn't work.
Any ideas?
Model binding is binding the object of your model class.
For example, ([Solution].[Models].[Model class]),
#model PassDatainMVC.Models.Record
To pass the data from controller to view,
Approach 1: ViewBag
Controller:
string data = "testing";
ViewBag.see = data;
return View();
View:
#using PassDatainMVC.Models
#ViewBag.see
Or:
Approach 2: Model binding
Controller (Class):
public string recordProperty;
View:
#model PassDatainMVC.Models.Record
#Model.recordProperty
While you have to set the property under the model class in the data field for the second approach.
Ref. https://www.c-sharpcorner.com/article/asp-net-mvc-passing-data-from-controller-to-view/
If you want to just one data you can use a ViewBag. This is simple.
Also you want to send with model. You should use this code.
Class
public class Calendar
{
public string CalendarName { get; set; }
}
Controller
Calendar newModel = new Calendar();
newModel.CalendarName = "test name...";
return View(newModel);
View
#model ModelNamespace.Calendar
<h1> #Model.CalendarName </h1>
Thanks Reha! But unfortunately neither of your suggestions did the trick.
For your first suggestion I used ViewBag. In the controller I replaced
return View((object)calendar);
to
ViewBag.calendar = calendar;
return View();
And replaced the view with just
#{ Layout = null; }
#ViewBag.calendar
The result was that the user is left looking at the actual HTML code instead of what the HTML code is supposed to render.
For your 2nd suggestion, I did exactly as you suggested (except I changed
Model.CalendarName = "test name...";
to
Model.CalendarName = calendar;
The result is the same, the user is left looking at the HTML code.

Problems with RenderAction in MVC 3

I wanted use MVC and renderpartial to generate a menu but but could not get it to work, and from what I read it seemed maybe RenderAction would be more suitable. Still I have not gotten it to work.
What I intended to do was create a controller that selects certain articles from a database that will act as categories (this is put into HomeController):
public ActionResult MenuController()
{
var movies = from m in db.Art
where m.ArtikelNr.StartsWith("Webcat")
select m;
return View(movies);
}
And then send that information to a view:
#model IEnumerable<xxxx.Models.Art>
#{
Layout = null;
}
<ul>
#foreach (var item in Model)
{
<li>#Html.DisplayFor(modelItem => item.Benämning_10)</li>
}
This works when I just run it as a normal controller and view, it returns a list of what I want. But if I want to call it from _layout.cshtml (because this menu should appear on every page) like this:
<div id="sidebar">#Html.RenderAction(MenuController)</div>
Then it generates the following error:
CS0103: The name 'MenuController' does not exist in the current context
What is the proper way of calling an action/view/whatever from the _layout.cshtml file?
You should call
#Html.RenderAction("_MenuController")
and be sure that you have a working rule in your Global.asax
As suggested in another answer would be better to use
return PartialView();
I also suggest you to use the ChildActionOnlyAttribute to be sure that this action will never be called as a standard action.
So something like that:
[ChildActionOnly]
public PartialViewResult _MenuController()
{
var movies = from m in db.Art
where m.ArtikelNr.StartsWith("Webcat")
select m;
return PartialView(movies);
}
#{Html.RenderAction("MenuController");}
or
#Html.Action("MenuController")
Simply
#Html.RenderAction("MenuController")
You've forgotten quotes around your string parameter
<div id="sidebar">#Html.RenderAction("_MenuController")</div>
Quotes around your action name :) It might also be good practice to return a partial view:
return PartialView(movies);

MVC3 Helper DisplayFor Model

I've got a view that displays a list of items. Rather than display items in a grid, I'd like to display 4 items per page, each with an image and multiple properties, displayed in a unique layout.
So, I'd like a foreach to iterate through the items, and each item to get displayed in a div. I could put all the code in the loop, but I'd like to have a custom html helper extension to do this.
I came up with this,
public static MvcHtmlString DisplayViewerFor(this HtmlHelper helper, TestModel model, bool rightAligned = true) {
if (model == null) {
model = new TestModel();
}
var outterDiv = new TagBuilder("div");
outterDiv.AddCssClass(rightAligned ? "item-display-right" : "item-display");
var image = new TagBuilder("image");
image.Attributes.Add("src", "Item/GetImage/" + model.ItemName);
image.Attributes.Add("height", "150");
var editorLabel = new TagBuilder("div");
editorLabel.AddCssClass("editor-label");
//LOOKING TO ADD CODE LIKE THIS HERE
var labelContent= html.LabelFor({my model property here})
editorLabel.InnerHtml += labelContent;
//END OF ADD
return new MvcHtmlString(outterDiv.ToString(TagRenderMode.EndTag));
}
In my method above, I need to display a few more values, and I would like to use the Html.LabelFor and Html.DisplayFor helpers, but the methods aren't available and I'm not sure what to pass to them if they were.
I'm not sure if this is possible or not, but I thought I would ask.
EDIT
I'm trying to use the html.LabelFor. See my code where I have updated it above, adding to it these two lines.
var labelContent= html.LabelFor({my model property here})
editorLabel.InnerHtml += labelContent;
You can see the code above.
EDIT 2
Here is the planned use for this Helper with dummied down view.
#model TestItemDisplayList
#{
ViewBag.Title = "Items";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#foreach(var item in #model.Items){
#Html.DisplayViewerFor(item)
}
You can use the Html.DisplayFor method to render a DisplayTemplate. One of the over loads for the method is to specify a template to use. You can modify your page code to read:
Page:
#model TestItemDisplayList
#{ ViewBag.Title = "Items"; Layout = "~/Views/Shared/_Layout.cshtml"; }
#Html.DisplayFor(model => Model,"TestItemDisplayList")
Display Template for TestItemDisplayList
#model TestItemDisplayList
#* You could limit this loop to the first 4 items *#
#foreach(var item in model.Items){ #Html.DisplayFor(item => item) }
Display Template for TestModel
#model TestModel
<div class="item-display">
<img src="#Url.Action("GetImage", "Image", new { id = Model.ItemName})" height="150"/>
<div class="editor-label">#Html.LabelFor(model => model.PropertyHere)</div>
</div>
I assume that your URL for the image used the default route of {controller}/{action}/{id} so I used the Url.Action and specified your ID.
You could also get away witout using a DisplayTemplate for "TestItemDisplayList" and moving that code in to your page but I wasn't clear if you wanted to add logic in that to limit the number of pages.

MVC3 Ajax.BeginForm odd behavior

I have a very simple view that has a DropDownListFor and a Button inside an Ajax.BeginForm helper. Clicking the button renders the whole view again inside the div I have set to update including the layout page (I also notice a spike in the cpu when clicking the button multiple times)
Here is the Ajax.BeginForm inside the view:
#using (Ajax.BeginForm("About2", "Home", new AjaxOptions { UpdateTargetId = "property22" }))
{
<div>
<div id="property22">
#Html.DropDownListFor(m => m.SomePropertyToBind, new SelectList(Model.list, "property1", "property2"))
</div>
<button type="submit" id="test">
Click me</button>
</div>
}
Any ideas where I'm going wrong?
I uploaded the whole project if someone has a couple of minutes to take a look at it:
http://www.sendspace.com/file/siu3r31 (free provider so there may be a popup)
Thanks
You are using a wrong overload of the Ajax.BeginForm helper. It should be like this:
#using (Ajax.BeginForm(
"About2",
"Home",
null,
new AjaxOptions { UpdateTargetId = "property22" },
new { #id = "refreshe" }
))
Notice the additional null value I am passing as the routeValues parameter. Also in the example you uploaded you forgot to include the TestView.cshtml view. This being said in order to fix the problem you have two possibilities:
Either return a partial view:
public ActionResult About2()
{
Random randomizer = new Random();
int random = randomizer.Next(1, 1000000000);
ModelTest newModelTest = new ModelTest();
string[] stringList = new string[] { "Red", "Blue", "Green" };
newModelTest.list = from test in stringList
select new ModelTestList
{
property1 = test,
property2 = test
};
newModelTest.SomePropertyToBind = stringList[random % 2];
return PartialView("TestView", newModelTest);
}
or disable the layout in the TestView.cshtml view:
#{
Layout = null;
}
Unfortunately from your explanation above and from the code, I am not sure what you are trying to achieve. However, I think your most worry is about having Ajax working in your view.
In your About2 action method, you are trying to return a complete view which is TestView (in that case, it doesnt exist) and passing it the newModelTest view Model. I would advise changing to return either a PartialView or JsonResult.
For example, changing the return statement of About2 action method to
public ActionResult About2()
{
...
return Json(newModelTest);
}
or changing it to a return type to string and returning "TestResult"
public String About2()
{
...
return "TestResult";
}
or you could change the return statement to return a PartialView
Thanks for your replies.
I just realized that About2 should have returned the "About" view instead of the "TestView". I had tried creating a partial view with the Ajax.BeginForm code but I came across the same problem.
This is my first attempt at Ajax.BeginForm (so far I have always used jquery), and I was under the impression that it works in a similar fashion in the sense that by specifying the target id only the contents of that element will get updated, not that the target will actually get replaced by the whole response object.
Thanks for your help, not only did I get it to work, but I now understand how it should work.
I suspect that what's happening is that you're returning the a complete View (including the layout template) in the Ajax response. Try changing your "Home" controller "About2" action temporarily to the following:
public ContentResult About2() {
return Content("Hello World");
}
I tested this sample Action with your Razor markup and clicking the button properly replaced your dropdown list with "Hello World!".
If this is indeed what's happening, then you'll want to return a View from "About2" without the layout by declaring the following at the top of the View that you're returning.
#{
Layout = null;
}

Modifying MVC 3 ViewBag in a partial view does not persist to the _Layout.cshtml

I am using MVC 3 with the Razor view engine. I want to set some values in the ViewBag inside a Partial View and want retrieve those values in my _Layout.cshtml. For example, when you setup a default ASP.NET MVC 3 project you get a _Layout.cshtml file in the "/Views/Shared" folder. In that _Layout.cshtml the Page Title is set like this:
<title>#ViewBag.PageTitle</title>
Then in "/Views/Home/About.cshtml" view the contents of the ViewBag are modified:
#{
ViewBag.Title = "About Us";
}
This works fine. When the About view is rendered the page title is "About Us". So, now I want to render a Partial view inside my About view and I want to modify the ViewBag.Title inside my Partial view. ("/Views/Shared/SomePartial.cshtml")
#Html.Partial("SomePartial")
In this Partial view I have this code:
#{
ViewBag.Title = "About Us From The Partial View";
}
When I debug this code I see the ViewBag.Title get set to "About Us" and then in the Partial view I see it get reset to "About Us From The Partial View", but when the code hits the _Layout.cshtml it goes back to "About Us".
Does this mean that if the contents of the ViewBag are modified in a Partial view, those changes will not appear(be accessible) in the main view (About.cshtml) or the _Layout.cshtml?
Thanks in advance!
If you pass the ViewBag into the partial's viewdatadictionary, then pull it out (and cast), you can do whatever you want and the reference is kept. The cool part is that since it's dynamic, you can even add properties and then they'll show up on the parent page's Viewbag.
Page:
//set the viewbag into the partial's view data
#{Html.RenderPartial("Elaborate", Model, new ViewDataDictionary { {"vb", ViewBag}});}
Partial:
#{
var vb = ((dynamic)ViewData["vb"]);
vb.TheTitle = "New values";
}
Page
#ViewBag.TheTitle = "New value"
I also had this problem, and couldn't find any neat and obvious solution.
The solution I came up with was to implement an Html extension method that returns a 'PageData' class that you define, containing whatever data you need:
[ThreadStatic]
private static ControllerBase pageDataController;
[ThreadStatic]
private static PageData pageData;
public static PageData GetPageData(this HtmlHelper html) {
ControllerBase controller = html.ViewContext.Controller;
while (controller.ControllerContext.IsChildAction) {
controller = controller.ControllerContext.ParentActionViewContext.Controller;
}
if (pageDataController == controller) {
return pageData;
} else {
pageDataController = controller;
pageData = new PageData();
return pageData;
}
}
It finds the top-level controller for the current request, and returns the same PageData object every time the method is called within the same HTTP request. It creates a new PageData object the first time it is called in a new HTTP request.
The partial view gets its own ViewBag.
You can get the page's ViewBag from ((WebViewPage) WebPageContext.Current.Page).ViewBag
You can do this trick in your partial view to override the title in your _Layout.cshtml:
#{
ViewBag.Title = "About Us From The Partial View";
}
......
<script type="text/javascript">
document.title = "#ViewBag.Title";
</script>
As others have pointed out Layout, Views and Partials get their own ViewBag. However, I was able to get it to work with the following:
In the View or Partial.
#{ Html.ViewContext.ViewBag.Title = "Reusable Global Variable"; }
Then in _Layout
#Html.ViewContext.ViewBag.Title
By explicitly using the ViewContext, the views 're-use' the same ViewBag.
If anyone is still looking for a solution to this it appears that you can do it with TempData:
TempData["x"] = x;
TempData is persisted until it is read so you can just read it in your _Layout. You just have to be careful that you read everything so that it is cleared for the next request.
I've tried the following and it works:
In the (parent) view...
#Html.Partial("SomePartial", ViewData, null)
Note: ViewData is passed as the model argument, but you have to specify null for the viewData argument to use the correct overload. You can't use ViewBag because Html.Partial doesn't like dynamics.
Then , in the partial view...
#model ViewDataDictionary
#{
Model["Title"] = "About us from the partial view";
}
Of course, if you need to use the model argument for a real model, you'll have to be more creative.
try #SLaks code with
(((WebViewPage) WebPageContext.Current.Page).ViewBag).PropertyName
I encountered the same problem when I use mvc3, and I found that
this.ViewBag.ViewBag.PropertyName
works in your custom control.
I this is what page data is designed for. Pop this into your view.
#Page.somePropertyName = "Whatever you want";
And then access it in your layout view. Be sure to check that its not null first.
#{
if(Page.somePropertyName != null)
{
//Do stuff
}
}

Resources