I have the following:
@model Pharma.ViewModels.SearchBoxViewModel
<div class="smart-search">
@using (Html.BeginForm("Index", "Search", FormMethod.Get, new { @class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<div class="hidden-xs- col-sm-1 col-md-1 col-lg-1 text-right">
@Html.LabelFor(m => m.SearchPhrase, new { @class = "control-label" })
</div>
<div class="col-xs-8 col-sm-8 col-md-9 col-lg-10">
@Html.TextBoxFor(m => m.SearchPhrase, new { @class = "form-control" })
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default" />
</div>
</div>
}
</div>
As you can see this is creating an input element.
The view model passed to the view contains the following:
public class SearchBoxViewModel
{
[Required]
[Display(Name = "Search")]
public string SearchPhrase { get; set; }
}
At the moment the input element contains a name attribute with the value of "SearchPhrase" but I would like the value to be just "q" without renaming the property.
I would prefer an extension which allows me to call TextBoxFor but without the need of having to supply the Name property, so that the custom attribute somehow sets the value of the Name property automatically to the value specified in the custom attribute.
The following is an example of what I mean:
public class SearchBoxViewModel
{
[Required]
[Display(Name = "Search")]
[Input(Name = "q")]
public string SearchPhrase { get; set; }
}
Combined with:
@model Pharma.ViewModels.SearchBoxViewModel
<div class="smart-search">
@using (Html.BeginForm("Index", "Search", FormMethod.Get, new { @class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<div class="hidden-xs- col-sm-1 col-md-1 col-lg-1 text-right">
@Html.LabelFor(m => m.SearchPhrase, new { @class = "control-label" })
</div>
<div class="col-xs-8 col-sm-8 col-md-9 col-lg-10">
@Html.TextBoxFor(m => m.SearchPhrase, new { @class = "form-control" })
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default" />
</div>
</div>
}
</div>
Which would then produce something similar to the following:
<div class="smart-search">
<form action="/Search/Index" method="get" class="form-horizontal" role="form">
<div class="form-group">
<div class="hidden-xs- col-sm-1 col-md-1 col-lg-1 text-right">
<label for="Search" class="control-label">Search</label>
</div>
<div class="col-xs-8 col-sm-8 col-md-9 col-lg-10">
<input type="text" name="q" id="Search" value="" class="form-control" />
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default" />
</div>
</div>
</form>
</div>
I would like this custom attribute to take effect whenever the SearchBoxViewModel is used regardless of what template is used to prevent errors, with the intention of being clear to programmers, while creating a user-friendly query string for the user.
Is it possible to do this using a custom attribute on the SearchPhrase property in a similar fashion to how the display name is changed?
The Razor syntax consists of Razor markup, C#, and HTML. Files containing Razor generally have a . cshtml file extension.
The Input Tag Helper binds an HTML <input> element to a model expression in your razor view. The Input Tag Helper: Generates the id and name HTML attributes for the expression name specified in the asp-for attribute. asp-for="Property1.
The difference between them is that View Pages are Razor views that are used to provide the HTML representations (aka views) for services in much the same way View Pages work for MVC Controllers.
I wrote something simple but can be a start to write the complete solution.
First I wrote a simple Attribute
with the name you provided:
public class InputAttribute : Attribute
{
public string Name { get; set; }
}
Then I wrote a Html helper that wraps default TextBoxFor
and searches for Input
attribute and if any, It will replace name attribute of generated HtmlString
from TextBoxFor
:
public static MvcHtmlString MyTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
{
var memberExpression = expression.Body as MemberExpression;
var attr = memberExpression.Member.GetCustomAttribute(typeof (InputAttribute)) as InputAttribute;
var result = htmlHelper.TextBoxFor(expression, htmlAttributes);
if (attr != null)
{
var resultStr = result.ToString();
var match = Regex.Match(resultStr, "name=\\\"\\w+\\\"");
return new MvcHtmlString(resultStr.Replace(match.Value, "name=\"" + attr.Name + "\""));
}
return result;
}
Then use this html helper in razor views:
@Html.MyTextBoxFor(m => m.SearchPhrase, new { @class = "form-control" })
Also your model is as follows:
public class SearchBoxViewModel
{
[Required]
[Display(Name = "Search")]
[Input(Name = "q")]
public string SearchPhrase { get; set; }
}
This is a way to complete solution:
TextBoxFor
SearchBoxViewModel
you will get a 404 because ModelBinder
can not bind request parameters to this ViewModel
. So you will have to write a ModelBinder
to solve this problem.LabelFor
accordingly to match for
attribute correctly.EDIT: In case of your problem you don't have to deal with case 2 because you send a GET
request and you will get the form parameters in query string. So you may write your action signature like:
public ActionResult Search(string q)
{
// use q to search
}
The problem occurs when you have a non-primitive type in your action parameters. In this case ModelBinder
tries to match query string items (Or request payload) with properties of type of action parameter. For example:
public ActionResult Search(SearchBoxViewModel vm)
{
// ...
}
In this case, query string (or request payload) has your search query in a parameter named q
(because name of input is q
and html form sends request in form of key-values consist of input name and input value). So MVC can not bind q
to SearchPhrase
in LoginViewModel
and you will get a 404.
I know this isn't what you are explicitly asking for but I feel that having a different ViewModel name from the actual form name undermines one of the core conventions in MVC and may be misleading.
As an alternative, you can just add a new property to the VM that mirrors SearchPhrase and has the proper name:
public class SearchBoxViewModel
{
[Required]
[Display(Name = "Search")]
public string SearchPhrase { get; set; }
[Display(Name = "Search")]
public string q
{
get { return SearchPhrase; }
set { SearchPhrase = value; }
}
}
Change your view to use these:
@model Pharma.ViewModels.SearchBoxViewModel
<div class="smart-search">
@using (Html.BeginForm("Index", "Search", FormMethod.Get, new { @class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<div class="hidden-xs- col-sm-1 col-md-1 col-lg-1 text-right">
@Html.LabelFor(m => m.q, new { @class = "control-label" })
</div>
<div class="col-xs-8 col-sm-8 col-md-9 col-lg-10">
@Html.TextBoxFor(m => m.q, new { @class = "form-control" })
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default" />
</div>
</div>
}
</div>
This would allow you to keep your code in the back end referencing SearchPhrase
instead of q
to make it easier on the programmers. Hopefully this view isn't spread out everywhere and you only have a single EditorTemplate or partial.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With