Add form HTML helpers and `MVCForm`

- copy over from legacy MVC
- fixup namespaces, remove copyright notices, ...
- temporarily remove `RouteBeginForm() overloads
- move main components to `HtmlHelper` and declare in `IHtmlHelper<T>`
- change `UrlHelper` to avoid treating an existing dictionary as an object
- add `HtmlHelper.Createform()` factory
- use in MVC sample; move BeginForm / TextBox samples to a new row
This commit is contained in:
dougbu 2014-04-07 12:04:28 -07:00
parent 94c028a5df
commit 1cd15fbf60
6 changed files with 259 additions and 20 deletions

View File

@ -134,8 +134,15 @@
</table>
</div>
<div style="float: left; border: thick solid lightskyblue; margin-left: 15px; padding-right: 10px">
<form action="/Home/Hello" method="post">
<div style="float: right; border: 5px solid red;">
@await Component.InvokeAsync("Tags", 15)
</div>
</div>
<div class="row">
<div style="float: left; border: thick solid lightskyblue; margin-bottom: 10px; margin-top: -180px; padding-right: 10px">
@using (Html.BeginForm(controllerName: "Home", actionName: "Hello", method: FormMethod.Post))
{
<table>
<tr>
<td>
@ -153,6 +160,16 @@
@Html.TextBoxFor(m => m.Address, htmlAttributes: new { @class = "form-control" })
</td>
</tr>
<tr>
<td>
<input type="submit" value="Save" class="btn btn-default" />
</td>
<td></td>
</tr>
</table>
}
@{ Html.BeginForm(method: FormMethod.Post, htmlAttributes: new { someAttribute = "some value", }); }
<table>
<tr>
<td>
<label class="control-label col-md-2">Anon.Name</label>
@ -191,21 +208,17 @@
</td>
<td>
@Html.TextBox("Anon.Address.Country", value: null, format: "{0} (4)",
htmlAttributes: new { @class = "form-control" })
htmlAttributes: new { @class = "form-control" })
</td>
</tr>
<tr>
<td>
<input type="submit" value="Save" class="btn btn-default" />
</td>
<td></td>
</tr>
</table>
<div class="form-group">
<div class="col-md-2">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</form>
</div>
<div style="float: left; border: 5px solid red;">
@await Component.InvokeAsync("Tags", 15)
@{ Html.EndForm(); }
</div>
</div>
@ -318,5 +331,4 @@
<div style="float: left; border: 5px solid green;">
@Html.DisplayForModel()
</div>
</div>
</div>

View File

@ -22,7 +22,15 @@ namespace Microsoft.AspNet.Mvc
public string Action(string action, string controller, object values)
{
var valuesDictionary = new RouteValueDictionary(values);
var valuesDictionary = values as IDictionary<string, object>;
if (valuesDictionary == null)
{
valuesDictionary = new RouteValueDictionary(values);
}
else
{
valuesDictionary = new RouteValueDictionary(valuesDictionary);
}
if (action != null)
{

View File

@ -139,6 +139,29 @@ namespace Microsoft.AspNet.Mvc.Rendering
ViewContext = viewContext;
}
public MvcForm BeginForm(string actionName, string controllerName, object routeValues, FormMethod method,
object htmlAttributes)
{
// Only need a dictionary if htmlAttributes is non-null. TagBuilder.MergeAttributes() is fine with null.
IDictionary<string, object> htmlAttributeDictionary = null;
if (htmlAttributes != null)
{
htmlAttributeDictionary = htmlAttributes as IDictionary<string, object>;
if (htmlAttributeDictionary == null)
{
htmlAttributeDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
}
}
return GenerateForm(actionName, controllerName, routeValues, method, htmlAttributeDictionary);
}
public void EndForm()
{
var mvcForm = CreateForm();
mvcForm.EndForm();
}
public string Encode(string value)
{
return (!string.IsNullOrEmpty(value)) ? WebUtility.HtmlEncode(value) : string.Empty;
@ -149,7 +172,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
return value != null ? WebUtility.HtmlEncode(value.ToString()) : string.Empty;
}
/// <inheritdoc />
public string FormatValue(object value, string format)
{
return ViewDataDictionary.FormatValue(value, format);
@ -183,7 +205,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
additionalViewData);
}
/// <inheritdoc />
public virtual HtmlString Name(string name)
{
var fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name);
@ -355,7 +376,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
}
/// <inheritdoc />
public HtmlString TextBox(string name, object value, string format, IDictionary<string, object> htmlAttributes)
{
return GenerateTextBox(metadata: null, name: name, value: value, format: format,
@ -367,6 +387,16 @@ namespace Microsoft.AspNet.Mvc.Rendering
return GenerateValue(name, value: null, format: format, useViewData: true);
}
/// <summary>
/// Override this method to return an <see cref="MvcForm"/> subclass. That subclass may change
/// <see cref="EndForm()"/> behavior.
/// </summary>
/// <returns>A new <see cref="MvcForm"/> instance.</returns>
protected virtual MvcForm CreateForm()
{
return new MvcForm(ViewContext);
}
protected string EvalString(string key, string format)
{
return Convert.ToString(ViewData.Eval(key, format), CultureInfo.CurrentCulture);
@ -397,6 +427,56 @@ namespace Microsoft.AspNet.Mvc.Rendering
return new Dictionary<string, object>();
}
/// <summary>
/// Writes an opening <form> tag to the response. When the user submits the form,
/// the request will be processed by an action method.
/// </summary>
/// <param name="actionName">The name of the action method.</param>
/// <param name="controllerName">The name of the controller.</param>
/// <param name="routeValues">An object that contains the parameters for a route. The parameters are retrieved
/// through reflection by examining the properties of the object. This object is typically created using object
/// initializer syntax. Alternatively, an <see cref="IDictionary{string, object}"/> instance containing the
/// route parameters.</param>
/// <param name="method">The HTTP method for processing the form, either GET or POST.</param>
/// <param name="htmlAttributes">An <see cref="IDictionary{string, object}"/> instance containing HTML
/// attributes to set for the element.</param>
/// <returns>An <see cref="MvcForm"/> instance which emits the closing {form} tag when disposed.</returns>
protected virtual MvcForm GenerateForm(string actionName, string controllerName, object routeValues,
FormMethod method, IDictionary<string, object> htmlAttributes)
{
var urlHelper = ViewContext.Url;
var formAction = urlHelper.Action(action: actionName, controller: controllerName, values: routeValues);
var 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), replaceExisting: true);
var traditionalJavascriptEnabled = ViewContext.ClientValidationEnabled &&
!ViewContext.UnobtrusiveJavaScriptEnabled;
if (traditionalJavascriptEnabled)
{
// TODO revive ViewContext.FormIdGenerator(), WebFx-199
// forms must have an ID for client validation
var formName = "form" + new Guid().ToString();
tagBuilder.GenerateId(formName, IdAttributeDotReplacement);
}
ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));
var theForm = CreateForm();
if (traditionalJavascriptEnabled)
{
ViewContext.FormContext.FormId = tagBuilder.Attributes["id"];
}
return theForm;
}
protected virtual HtmlString GenerateTextBox(ModelMetadata metadata, string name, object value, string format,
IDictionary<string, object> htmlAttributes)
{

View File

@ -0,0 +1,49 @@
using System;
namespace Microsoft.AspNet.Mvc.Rendering
{
public class MvcForm : IDisposable
{
private readonly ViewContext _viewContext;
private bool _disposed;
public MvcForm([NotNull] ViewContext viewContext)
{
_viewContext = viewContext;
// Push the new FormContext; GenerateEndForm() does the corresponding pop.
_viewContext.FormContext = new FormContext();
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Renders the closing </form> tag to the response.
/// </summary>
public void EndForm()
{
Dispose(disposing: true);
}
protected virtual void GenerateEndForm()
{
_viewContext.Writer.Write("</form>");
// TODO revive viewContext.OutputClientValidation(), this requires GetJsonValidationMetadata(), GitHub #163
_viewContext.FormContext = null;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
GenerateEndForm();
}
}
}
}

View File

@ -0,0 +1,67 @@

namespace Microsoft.AspNet.Mvc.Rendering
{
public static class HtmlHelperFormExtensions
{
public static MvcForm BeginForm<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper)
{
// Generates <form action="{current url}" method="post">.
return htmlHelper.BeginForm(actionName: null, controllerName: null, routeValues: null,
method: FormMethod.Post, htmlAttributes: null);
}
public static MvcForm BeginForm<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, FormMethod method)
{
return htmlHelper.BeginForm(actionName: null, controllerName: null, routeValues: null,
method: method, htmlAttributes: null);
}
public static MvcForm BeginForm<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, FormMethod method,
object htmlAttributes)
{
return htmlHelper.BeginForm(actionName: null, controllerName: null, routeValues: null,
method: method, htmlAttributes: htmlAttributes);
}
public static MvcForm BeginForm<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, object routeValues)
{
return htmlHelper.BeginForm(actionName: null, controllerName: null, routeValues: routeValues,
method: FormMethod.Post, htmlAttributes: null);
}
public static MvcForm BeginForm<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string actionName,
string controllerName)
{
return htmlHelper.BeginForm(actionName, controllerName, routeValues: null,
method: FormMethod.Post, htmlAttributes: null);
}
public static MvcForm BeginForm<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string actionName,
string controllerName, object routeValues)
{
return htmlHelper.BeginForm(actionName, controllerName, routeValues,
FormMethod.Post, htmlAttributes: null);
}
public static MvcForm BeginForm<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string actionName,
string controllerName, FormMethod method)
{
return htmlHelper.BeginForm(actionName, controllerName, routeValues: null,
method: method, htmlAttributes: null);
}
public static MvcForm BeginForm<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string actionName,
string controllerName, object routeValues, FormMethod method)
{
return htmlHelper.BeginForm(actionName, controllerName, routeValues,
method, htmlAttributes: null);
}
public static MvcForm BeginForm<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string actionName,
string controllerName, FormMethod method, object htmlAttributes)
{
return htmlHelper.BeginForm(actionName, controllerName, routeValues: null,
method: method, htmlAttributes: htmlAttributes);
}
}
}

View File

@ -31,6 +31,29 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// </summary>
ViewDataDictionary<TModel> ViewData { get; }
/// <summary>
/// Writes an opening <form> tag to the response. When the user submits the form,
/// the request will be processed by an action method.
/// </summary>
/// <param name="actionName">The name of the action method.</param>
/// <param name="controllerName">The name of the controller.</param>
/// <param name="routeValues">An object that contains the parameters for a route. The parameters are retrieved
/// through reflection by examining the properties of the object. This object is typically created using object
/// initializer syntax. Alternatively, an <see cref="IDictionary{string, object}"/> instance containing the
/// route parameters.</param>
/// <param name="method">The HTTP method for processing the form, either GET or POST.</param>
/// <param name="htmlAttributes">An object that contains the HTML attributes to set for the element.
/// Alternatively, an <see cref="IDictionary{string, object}"/> instance containing the HTML attributes.
/// </param>
/// <returns>An <see cref="MvcForm"/> instance which emits the closing {form} tag when disposed.</returns>
MvcForm BeginForm(string actionName, string controllerName, object routeValues, FormMethod method,
object htmlAttributes);
/// <summary>
/// Renders the closing </form> tag to the response.
/// </summary>
void EndForm();
/// <summary>
/// Returns HTML markup for each property in the object that is represented by the expression, using the specified
/// template, HTML field ID, and additional view data.