PartialView Dynamic BeginForm Parameters - asp.net-mvc

If I have the below PartialView
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.Photo>" %>
<% using (Html.BeginForm("MyAction", "MyController", FormMethod.Post, new { enctype = "multipart/form-data" })) { %>
<%= Html.EditorFor( c => c.Caption ) %>
<div class="editField">
<label for="file" class="label">Select photo:</label>
<input type="file" id="file" name="file" class="field" style="width:300px;"/>
</div>
<input type="submit" value="Add photo"/>
<%} %>
As you can see, the Action and the Controller are hard coded. Is there a way I can make them dynamic?
My goal is to have this partial view generic enough that I can use it in many places and have it submit to the Action and Controller it is sitting within.
I know I can use ViewData but really don't want to and likewise with passing a VormViewModel to the view and using the model properties.
Is there a nicer way than the two I listed above?

I Checked the source code of MVC and dig into the System.Web.Mvc --> Mvc --> Html --> FormExtensions so I find you can write some code like :
public static class FormHelpers
{
public static MvcForm BeginFormImage(this HtmlHelper htmlHelper, IDictionary<string, object> htmlAttributes)
{
string formAction = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
return FormHelper(htmlHelper, formAction, FormMethod.Post, htmlAttributes);
}
public static MvcForm FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes)
{
TagBuilder tagBuilder = new TagBuilder("form");
tagBuilder.MergeAttributes(htmlAttributes);
// action is implicitly generated, so htmlAttributes take precedence.
tagBuilder.MergeAttribute("action", formAction);
tagBuilder.MergeAttribute("enctype", "multipart/form-data");
// method is an explicit parameter, so it takes precedence over the htmlAttributes.
tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));
MvcForm theForm = new MvcForm(htmlHelper.ViewContext);
if (htmlHelper.ViewContext.ClientValidationEnabled)
{
htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"];
}
return theForm;
}
}
I'm not sure this is exactly what you really want to get but I'm sure you can get it if you change this lines in way satisfies your needs.
Hope this helps.

Related

How can I use an #HtmlHelper inside a custom #HtmlHelper?

I am trying to create a custom Html helper with ASP.NET MVC. I have the following code:
#helper DefaultRenderer(Models.Control control)
{
<div class="form-group">
<label class="control-label" for="#control.Name">#control.Label</label>
#Html.TextBoxFor(m => control.Value, new { #class = "form-control" })
</div>
}
Apparently #Html.TextBoxFor cannot be found inside a Helper .cshtml class. I can use it in a partial view which is also a .cshtml class.
I can use #HtmlTextBox but then I will lose the strong model binding...
Why is this happening and is there a way to get it to work?
No, this is not possible. You could not write a normal HTML helper with #Html.TextBoxFor because that your view is strongly typed.
So you need something like:
public class HelperExtentions{
public static MvcHtmlString DefaultRenderer<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Control control , object htmlAttributes)
{
var sb = new StringBuilder();
var dtp = htmlHelper.TextBoxFor(expression, htmlAttributes).ToHtmlString();
sb.AppendFormat("<div class='form-group'><label class='control-label' for='{1}'>{2}</label>{0}</div>", dtp,control.Name,control.Label);
return MvcHtmlString.Create(sb.ToString());
}
}
Then you can use :
#html.DefaultRenderer(m => m.Control.Value, Models.Control,new { #class = "form-control" }

binding inside html helper

i want to bind to the display style property of my Html.TextBox
<%= Html.TextBox("RunDates", Model.RunDates, new { style = "display: none" })%>
is this possible? i want to do something like the following that i know works for me:
<input id="btnBack" class="btnAction" type="button" value="Back" style="display: <%= Model.IsEnabledProductSetupBack?"inline":"none" %>;" />
or is there a way to post <input type="text" ... in mvc?
You could write a custom Html helper for this. There are 2 possibilities:
You replace this weakly typed TextBox helper with strongly typed TextBoxFor by taking advantage of a view model (which is what I would recommend) in which case you will be able to write your textbox like this:
<%= Html.MyTextBoxFor(x => x.RunDates) %>
You stick with weak typing in which case the best you could get is this:
<%= Html.TextBox("RunDates", Model.RunDates, Html.SelectStyle(Model.IsEnabledProductSetupBack)) %>
Now since I recommend the first solution, it is the one I would provide same code for:
public static class HtmlExtensions
{
public static IHtmlString MyTextBoxFor<TProperty>(
this HtmlHelper<MyViewModel> helper,
Expression<Func<MyViewModel, TProperty>> ex
)
{
var model = helper.ViewData.Model;
var htmlAttributes = new RouteValueDictionary();
htmlAttributes["style"] = "display: none;";
if (model.IsEnabledProductSetupBack)
{
htmlAttributes["style"] = "display: inline;";
}
return helper.TextBoxFor(ex, htmlAttributes);
}
}

Creating Some thing #using (Html.BeginForm()) Helpers

In Asp.net MVC3 when you write below code , it generates wrapping html itself
#using (Html.BeginForm()) {
#Html.ValidationMessageFor(model => model.Text)
}
It generates codes in below format,
<form method="post" action="/Feeds">
<!-- Fields Here -->
</form>
My question in #using (Html.BeginForm()) automatically adds <form> tag at beginning and end, how can i create something like that of my own.
I am looking for some thing like below
#using (Html.BeginMYCUSTOMDIV())
{
I am text inside div
}
Expected Generated Output
<div class="customDivClass">
I am text inside div
</div>
Something along the lines:
public class MyDiv : IDisposable
{
private readonly TextWriter _writer;
public MyDiv(TextWriter writer)
{
_writer = writer;
}
public void Dispose()
{
_writer.WriteLine("</div>");
}
}
public static class MyExtensions
{
public static MyDiv BeginMYCUSTOMDIV(this HtmlHelper htmlHelper)
{
var div = new TagBuilder("div");
div.AddCssClass("customDivClass");
htmlHelper.ViewContext.Writer.WriteLine(div.ToString(TagRenderMode.StartTag));
return new MyDiv(htmlHelper.ViewContext.Writer);
}
}
and in the view:
#using (Html.BeginMYCUSTOMDIV())
{
<span>Hello</span>
}
generates:
<div class="customDivClass">
<span>Hello</span>
</div>
If I'm not mistaken, Html.BeginForm() returns an IDisposable object. When used in the using block, the object's Disposemethod is called, which is the responsible to write the closing tag to the output.
how does using HtmlHelper.BeginForm() work?
Html.BeginForm() type of extension

Html.BeginForm in MVC3 is rendering too much

I have the following code. Note that I am not using a using as I want to begin and end the form in two different areas of my code. Has anyone seen this action where the helper adds "System.Web.Mvc.Html.MvcForm". I cannot see why this is added.
Code:
#Html.BeginForm(new { action = ViewContext.Controller.ValueProvider.GetValue("action").RawValue })
#{ Html.EndForm() }
Result:
<form action="/adminTests/Edit" method="post">System.Web.Mvc.Html.MvcForm
When I use the following I don't get the "System.Web.Mvc.Html.MvcForm"
#using (Html.BeginForm(new { action = ViewContext.Controller.ValueProvider.GetValue("action").RawValue }))
{
}
You need to wrap that in a using statement. BeginForm writes directly to the response stream, it isn't intended to return a string like the other helpers. The way you are using it, it is writing it's output, then returning the MvcForm object which, when disposed, will write the closing form tag. Instead, your syntax forces it to call ToString on the returned object resulting in the output you see.
#using(Html.BeginForm(...
{
...
}
Edit: Since you can't wrap it in a using statement, you need to either explicitly close the form with HTML or call EndForm later. In either case you have to use the BeginForm (and EndForm) in a code block instead of using it with the string output syntax. Both of these methods write directly to the response stream.
#{ Html.BeginForm() .. }
</form> or #{ Html.EndForm() }
Here's the relevant bits of the code for those who think I'm wrong (without permission):
public static void EndForm(this HtmlHelper htmlHelper) {
HttpResponseBase httpResponse = htmlHelper.ViewContext.HttpContext.Response;
httpResponse.Write("</form>"); // <-- look ma, Response.Write()
}
private static MvcForm FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes) {
TagBuilder tagBuilder = new TagBuilder("form");
tagBuilder.MergeAttributes(htmlAttributes);
// action is implicitly generated, so htmlAttributes take precedence.
tagBuilder.MergeAttribute("action", formAction);
// method is an explicit parameter, so it takes precedence over the htmlAttributes.
tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
HttpResponseBase httpResponse = htmlHelper.ViewContext.HttpContext.Response;
httpResponse.Write(tagBuilder.ToString(TagRenderMode.StartTag)); <-- and here
return new MvcForm(htmlHelper.ViewContext.HttpContext.Response);
}
The BeginForm/EndForm helpers don't return a IHtmlResult so you should use them like this:
#{ Html.BeginForm(new { action = ViewContext.Controller.ValueProvider.GetValue("action").RawValue }); }
...
#{ Html.EndForm(); }
Try something like this:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<p>
#Html.LabelFor(model => model.Username) <br />
#Html.EditorFor(model => model.Username)
#Html.ValidationMessageFor(model => model.Username)
</p>
<p>
#Html.LabelFor(model => model.Password) <br />
#Html.EditorFor(model => model.Password) #Html.RouteLink("Forgot Password", "password") <br />
#Html.ValidationMessageFor(model => model.Password)
</p>
<p class="mtop10">
<div class="float-left small blue super-button">
<a class="auto-submit" href="#">Login</a>
</div>
<div class="alt-action">
or #Html.RouteLink("Register", "register")
</div>
</p>
</fieldset>
}
I also saw System.Web.Mvc.Html.MvcForm when using:
Html.BeginForm("Index", "Home", FormMethod.Post)
The following change fixed it:
Html.BeginForm("Index", "Home", "POST");
Seems like FormMethod.Post forces the unwanted output but I can't explain it better than that. The change worked for me though.

ASP.NET MVC partial views: input name prefixes

Suppose I have ViewModel like
public class AnotherViewModel
{
public string Name { get; set; }
}
public class MyViewModel
{
public string Name { get; set; }
public AnotherViewModel Child { get; set; }
public AnotherViewModel Child2 { get; set; }
}
In the view I can render a partial with
<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>
In the partial I'll do
<%= Html.TextBox("Name", Model.Name) %>
or
<%= Html.TextBoxFor(x => x.Name) %>
However, the problem is that both will render name="Name" while I need to have name="Child.Name" in order for model binder to work properly. Or, name="Child2.Name" when I render the second property using the same partial view.
How do I make my partial view automatically recognize the required prefix? I can pass it as a parameter but this is too inconvenient. This is even worse when I want for example to render it recursively. Is there a way to render partial views with a prefix, or, even better, with automatic reconition of the calling lambda expression so that
<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>
will automatically add correct "Child." prefix to the generated name/id strings?
I can accept any solution, including 3-rd party view engines and libraries - I actually use Spark View Engine (I "solve" the problem using its macros) and MvcContrib, but did not find a solution there. XForms, InputBuilder, MVC v2 - any tool/insight that provide this functionality will be great.
Currently I think about coding this myself but it seems like a waste of time, I can't believe this trivial stuff is not implemented already.
A lot of manual solutions may exists, and all of them are welcome. For example, I can force my partials to be based off IPartialViewModel<T> { public string Prefix; T Model; }. But I'd rather prefer some existing/approved solution.
UPDATE: there's a similar question with no answer here.
You can extend Html helper class by this :
using System.Web.Mvc.Html
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
string name = ExpressionHelper.GetExpressionText(expression);
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = name
}
};
return helper.Partial(partialViewName, model, viewData);
}
and simply use it in your views like this :
<%= Html.PartialFor(model => model.Child, "_AnotherViewModelControl") %>
and you will see everything is ok!
so far, i was searching for the same thing I have found this recent post:
http://davybrion.com/blog/2011/01/prefixing-input-elements-of-partial-views-with-asp-net-mvc/
<% Html.RenderPartial("AnotherViewModelControl", Model.Child, new ViewDataDictionary
{
TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "Child1" }
})
%>
My answer, based on the answer of Mahmoud Moravej including the comment of Ivan Zlatev.
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
string name = ExpressionHelper.GetExpressionText(expression);
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
StringBuilder htmlFieldPrefix = new StringBuilder();
if (helper.ViewData.TemplateInfo.HtmlFieldPrefix != "")
{
htmlFieldPrefix.Append(helper.ViewData.TemplateInfo.HtmlFieldPrefix);
htmlFieldPrefix.Append(name == "" ? "" : "." + name);
}
else
htmlFieldPrefix.Append(name);
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = htmlFieldPrefix.ToString()
}
};
return helper.Partial(partialViewName, model, viewData);
}
Edit:
The Mohamoud's answer is incorrect for nested partial rendering. You need to append the new prefix to the old prefix, only if it is necessary. This was not clear in the latest answers (:
Using MVC2 you can achieve this.
Here is the strongly typed view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcLearner.Models.Person>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Create
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Create</h2>
<% using (Html.BeginForm()) { %>
<%= Html.LabelFor(person => person.Name) %><br />
<%= Html.EditorFor(person => person.Name) %><br />
<%= Html.LabelFor(person => person.Age) %><br />
<%= Html.EditorFor(person => person.Age) %><br />
<% foreach (String FavoriteFoods in Model.FavoriteFoods) { %>
<%= Html.LabelFor(food => FavoriteFoods) %><br />
<%= Html.EditorFor(food => FavoriteFoods)%><br />
<% } %>
<%= Html.EditorFor(person => person.Birthday, "TwoPart") %>
<input type="submit" value="Submit" />
<% } %>
</asp:Content>
Here is the strongly typed view for the child class (which must be stored in a subfolder of the view directory called EditorTemplates):
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MvcLearner.Models.TwoPart>" %>
<%= Html.LabelFor(birthday => birthday.Day) %><br />
<%= Html.EditorFor(birthday => birthday.Day) %><br />
<%= Html.LabelFor(birthday => birthday.Month) %><br />
<%= Html.EditorFor(birthday => birthday.Month) %><br />
Here is the controller:
public class PersonController : Controller
{
//
// GET: /Person/
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index()
{
return View();
}
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Create()
{
Person person = new Person();
person.FavoriteFoods.Add("Sushi");
return View(person);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Person person)
{
return View(person);
}
}
Here are the custom classes:
public class Person
{
public String Name { get; set; }
public Int32 Age { get; set; }
public List<String> FavoriteFoods { get; set; }
public TwoPart Birthday { get; set; }
public Person()
{
this.FavoriteFoods = new List<String>();
this.Birthday = new TwoPart();
}
}
public class TwoPart
{
public Int32 Day { get; set; }
public Int32 Month { get; set; }
}
And the output source:
<form action="/Person/Create" method="post"><label for="Name">Name</label><br />
<input class="text-box single-line" id="Name" name="Name" type="text" value="" /><br />
<label for="Age">Age</label><br />
<input class="text-box single-line" id="Age" name="Age" type="text" value="0" /><br />
<label for="FavoriteFoods">FavoriteFoods</label><br />
<input class="text-box single-line" id="FavoriteFoods" name="FavoriteFoods" type="text" value="Sushi" /><br />
<label for="Birthday_Day">Day</label><br />
<input class="text-box single-line" id="Birthday_Day" name="Birthday.Day" type="text" value="0" /><br />
<label for="Birthday_Month">Month</label><br />
<input class="text-box single-line" id="Birthday_Month" name="Birthday.Month" type="text" value="0" /><br />
<input type="submit" value="Submit" />
</form>
Now this is complete. Set a breakpoint in the Create Post controller action to verify. Don't use this with lists however because it wont work. See my question on using EditorTemplates with IEnumerable for more on that.
This is an old question, but for anyone arriving here looking for a solution, consider using EditorFor, as suggested in a comment in https://stackoverflow.com/a/29809907/456456. To move from a partial view to an editor template, follow these steps.
Verify that your partial view is bound to ComplexType.
Move your partial view to a subfolder EditorTemplates of the current view folder, or to the folder Shared. Now, it is an editor template.
Change #Html.Partial("_PartialViewName", Model.ComplexType) to #Html.EditorFor(m => m.ComplexType, "_EditorTemplateName"). The editor template is optional if it's the only template for the complex type.
Html Input elements will automatically be named ComplexType.Fieldname.
PartailFor for asp.net Core 2 in case someone needs it.
public static ModelExplorer GetModelExplorer<TModel, TResult>(this IHtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TResult>> expression)
{
if (expression == null)
throw new ArgumentNullException(nameof(expression));
return ExpressionMetadataProvider.FromLambdaExpression(expression, htmlHelper.ViewData, htmlHelper.MetadataProvider);
}
public static IHtmlContent PartialFor<TModel, TResult>(this IHtmlHelper<TModel> helper, Expression<Func<TModel, TResult>> expression, string partialViewName, string prefix = "")
{
var modelExplorer = helper.GetModelExplorer(expression);
var viewData = new ViewDataDictionary(helper.ViewData);
viewData.TemplateInfo.HtmlFieldPrefix += prefix;
return helper.Partial(partialViewName, modelExplorer.Model, viewData);
}
As stated here: https://stackoverflow.com/a/58943378/3901618 - for ASP.NET Core - you can use the partial tag helper.
<partial name="AnotherViewModelControl" for="Child" />
<partial name="AnotherViewModelControl" for="Child2" />
It generates all required name prefixes.
I came across this issue also and after much pain i found it was easier to redesign my interfaces such that i didn't need to post back nested model objects. This forced me to change my interface workflows: sure i now require the user to do in two steps what i dreamed of doing on one, but the usability and code maintainability of the new approach is of greater value to me now.
Hope this helps some.
You could add a helper for the RenderPartial which takes the prefix and pops it in the ViewData.
public static void RenderPartial(this HtmlHelper helper,string partialViewName, object model, string prefix)
{
helper.ViewData["__prefix"] = prefix;
helper.RenderPartial(partialViewName, model);
}
Then a further helper which concatenates the ViewData value
public static void GetName(this HtmlHelper helper, string name)
{
return string.Concat(helper.ViewData["__prefix"], name);
}
and so in the view ...
<% Html.RenderPartial("AnotherViewModelControl", Model.Child, "Child.") %>
in the partial ...
<%= Html.TextBox(Html.GetName("Name"), Model.Name) %>
Like you, I add Prefix property (a string) to my ViewModels which I append before my model bound input names. (YAGNI preventing the below)
A more elegant solution might be a base view model that has this property and some HtmlHelpers that check if the view model derives from this base and if so append the prefix to the input name.
Hope that helps,
Dan
How about just before you call RenderPartial you do
<% ViewData["Prefix"] = "Child."; %>
<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>
Then in your partial you have
<%= Html.TextBox(ViewData["Prefix"] + "Name", Model.Name) %>

Resources