I have a strong typed view from a model class. I would like to add there a text field where I can pass a number. This number should told the controller, how often the object should be added to the database. But in the View I only have the submit button and my controller function handles this postback
' POST: /Adminpanel/AddHardware '
<Authorize()>
<HttpPost>
Function AddHardware(ByVal hw As Hardware) As ActionResult
If ModelState.IsValid Then
db.Hardware.Add(hw)
db.SaveChanges()
Response.Redirect("~/Adminpanel/Hardware")
Else
Response.Redirect("~/Adminpanel/Hardware")
End If
End Function
it should be look like:
' POST: /Adminpanel/AddHardware '
<Authorize()>
<HttpPost>
Function AddHardware(ByVal hw As Hardware, ByVal amount As Integer) As ActionResult
If ModelState.IsValid Then
For i As Integer = 0 To amount
db.Hardware.Add(hw)
db.SaveChanges()
Next
Response.Redirect("~/Adminpanel/Hardware")
Else
Response.Redirect("~/Adminpanel/Hardware")
End If
End Function
How can I add this text field in my view and pass the value to the controller?
Add a input element in your view (inside your form) and give it name="amount". And when the form is submitted MVC will automatically bind the value from view to your controller. You can access the value of amount in controller directly.
NOTE: The binding depends on the name of the element. If your input tag has the name amount and in your controller parameters if you have a parameter amount MVC will bind the value to this parameter. So always make sure your element name and the parameter name are the same.
You should be able to simply add an <input ... /> to your form with an id of amount, and MVC will take care of binding the value to your amount parameter.
Related
I have a template editor, Currency.cshtml like this:
#model decimal?
...
string value =
// Some calculations that returns value formatted as currency
Currency: #value<br/>
#Html.TextBox("", value, attributes)
I have a view which uses this template like this:
#Html.EditorFor(m => m.Amount.Value, "Currency", someAdditionalViewData)
When this view is rendered as a partial directly inside another view, the result is as expected: both the text, and the editor show the formatted variable "value", like so:
Currency: 1.223,18 <== well formatted
[ 1.223,18] <== this is the input type=text, well formatted
However, if I get the view using Ajax (Ajax.ActionLink), I get the firs part formatted, but the second one unformatted, like so:
Currency: 1.223,18 <== well formatted
[ 1223.18] <== this is the input type=text, not formatted!!
Any idea why this can be going on? Should I change the final #Html.TextBox("", value, attributes) in my template for something else?
I don't know the reason, but after investigating for some time, I can assure that the final call to #Html.TextBox("", value, attributes), on an Ajax request behaves in a different way than on a PartialView render.
In a PartialView render, it creates the desired input element, with the provided formatted value. However, in an ajax request, it looks up for info on the model and creates its own version of the textbox, including the unobstrusive validation attributes and so on.
The only way to solve this problem is not to use the TextBox method, but create an input writing it directly with a TagBuilder. Pay attention to the way of getting the Id and name for the input element.
#{
string value = ... // Format as desired
// Create the required attributes
// NOTE: HtmlAttributes is simply a custom "fluent" attribute builder,
// inherited from IDictionay<string,object>
var attributes = HtmlAttributes.Empty
.Add("type", "text")
.Add("value", value) // formatted value
.Add("id", ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(""))
.Add("name", ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(""))
.AddStyle("text-align", "right");
// You can add extra attributes, i.e. unobtrusive validation, provided by user...
var extraAttibutes = ...
attributes.Merge(extraAttributes);
// Use the tag builder to create the input
TagBuilder tb = new TagBuilder("input");
foreach (var attribute in attributes)
{
tb.Attributes.Add(attribute.Key, attribute.Value.ToString());
}
var textBox = tb.ToString(TagRenderMode.SelfClosing);
// Then, either write it directly to the writer...
ViewContext.Writer.Write(textBox);
}
#* ...or use Html.Raw *#
#Html.Raw(textBox)
Of course, there are a lot of details omitted (how to get unobtrusive or any other extra attributes for the tag and so on). The best soultion would be to discover why the behavior changes from a "complete" render and an ajax request render. All the View metadata looks exactly the same, but works in a different way.
I also had a partialview that used an editortemplate which worked when initially loading the page. However when using ajax to call for the partial the editortemplate wasn't used.
For me turned out I had to move it to a different folder:
From
Views/[Controllername]/EditorTemplates
To
Views/Shared/EditorTemplates
I have an get action, which has a couple of parameters. In view I have a form, that fills after get action. Action creates an instance of model using received parameters, but if parameter has special value then action set to model some default values despite parameters. After that action return view with model.
The problem is that after changing the model in View shows data of the parameters, and not from the modified model.
This happens because, by design, all HTML helpers first look at the ModelState when binding and after that in the model itself. So if you intend to modify some value that was part of the POST request you will need to remove it from ModelState first if you want this change to be reflected in the view:
[HttpPost]
public ActionResult SomeAction(MyViewModel model)
{
// We remove the Bar property that was part of the request because
// we modify its value here
ModelState.Remove("Bar");
model.Bar = "Some modified value";
return View(model);
}
This assumes that in the corresponding view you have an input field for it:
#Html.EditorFor(x => x.Bar)
I am writing my first ASP.Net webpage and using MVC.
I have a string that I am building in a partial view with a grid control (DevExpress MVCxGridView). In my partial view I am using a HTML.Hidden helper as shown below.
' Create a hidden variable to pass back a comma-delimited string
Response.Write(Html.Hidden( "exclusionList", Model.ExclusionList))
The value of of this hidden element is assigned in client side javaScript:
exclusionListElement = document.getElementById("exclusionList");
// ...
exclusionString = getExclusionString();
exclusionListElement.value = exclusionString;
This seems to work without problem.
In my controller action method:
<AcceptVerbs( HttpVerbs.Post )> _
Public Function MyPartialCallback(updatedItemList As myModel) As ActionResult
Dim myData As myModel = GetMyModel()
Return PartialView( "MyPartial", myModel.myList )
End Function
The updatedItemList parameter is always nothing and exclusion list exists no where in the Request.Forms.
My questions are:
What is the correct way to use Html.Hidden so that I can access data in a MVC Controller Action method.
Is adding "cargo" variables to Request.Form the best and only way to send data back to a server side MVC Controller Action method? It just seems like twine and duct-tape approach. Is there a more structured approach?
If you need to get the exclusionList variable back, you just need to add a property to your view model that matches that name exactly. Make sure it is of the correct type (string it looks like in this case) and then it should auto populate that property in the view model for you.
And yes, there is no need for the Response.Write call. Instead just use the Html.HiddenFor(...) helper in your view.
Look at the generated HTML. Note down the name attribute of the hidden field. Use this name as action parameter name:
Public Function MyPartialCallback(exclusionList As string)
Is it possible to disable a certain action parameter from retaining its value across requests?
[HttpPost]
public ActionResult MyAction(string value1, string value2)
{
if(value1=="hi")
ModelState.AddModelError("value1", "Can't have hi");
//do stuff
if(ModelState.IsValid)
return RedirectToAction("Finish");
else
return View()
}
[HttpGet]
public ActionResult MyAction()
{
return View()
}
The view consists of a simple form with two input boxes (value1 and value2). Once submitted and validation fails, the view is returned. I want to always have the value of the textbox in the view to be empty.
The value for the textbox "value1" is retained if the the model is invalidated.
I tried to declare the textbox as <%= Html.TextBox("value1", null) %> but the value is still retained. I also tried to use [Bind(Exclude="value1")] but that dosen't work on a single variable.
Update 2:
I'm doing this for a textbox that is used for Captcha (custom solution) input. I want the textbox to be cleared any time the page is loaded, but I want validation to remain.
Try calling
ModelState["value1"].Value
= new ValueProviderResult(null, string.Empty, CultureInfo.InvariantCulture);
before you return the view from within your controller action.
What this does is keep all the errors associated with the key "value1", but replaces the value with an empty value.
What are you doing that's causing it to be retained? There isn't anything like ViewState in MVC that will persist a value over multiple requests unless you're writing code or using form fields to make it do so.
What does the view look like? Is this action method being called via GET or POST? What's the "do stuff" contained in your method?
Edit: You're still showing //do stuff in your example code. Does that stuff contain any references to ViewData? Your question is about binding, but I don't see any binding happening. Maybe this is beyond my understanding.
Edit 2: Glad Phil saw this one! The original question didn't mention the ModelState.
I have a controller with two actions:
[AcceptVerbs("GET")]
public ActionResult Add()
{
PrepareViewDataForAddAction();
return View();
}
[AcceptVerbs("POST")]
public ActionResult Add([GigBinderAttribute]Gig gig, FormCollection formCollection)
{
if (ViewData.ModelState.IsValid)
{
GigManager.Save(gig);
return RedirectToAction("Index", gig.ID);
}
PrepareViewDataForAddAction();
return View(gig);
}
As you can see, when the form posts its data, the Add action uses a GigBinder (An implemenation of IModelBinder)
In this binder I have:
if (int.TryParse(bindingContext.HttpContext.Request.Form["StartDate.Hour"], out hour))
{
gig.StartDate.Hour = hour;
}
else
{
bindingContext.ModelState.AddModelError("Doors", "You need to tell us when the doors open");
}
The form contains a text box with id "StartDate.Hour".
As you can see above, the GigBinder tests to see that the user has typed in an integer into the textbox with id "StartDate.Hour". If not, a model error is added to the modelstate using AddModelError.
Since the gigs property gigs.StartDate.Hour is strongly typed, I cannot set its value to, for example, "TEST" if the user has typed this into the forms textbox.
Hence, I cant set the value of gigs.StartDate.Hour since the user has entered a string rather than an integer.
Since the Add Action returns the view and passes the model (return View(gig);) if the modelstate is invalid, when the form is re-displayed with validation mssages, the value "TEST" is not displayed in the textbox. Instead, it will be the default value of gig.StartDate.Hour.
How do I get round this problem? I really stuck!
I think the problem is that your ViewModel does not match closely enough with your View. It's really important in MVC that your ViewModel matches your View as closely as possible.
In your ViewModel you're assuming an integer, but in your View you're using a TextBox to render the property, which will allow any kind of text. There's a mismatch here and the difficulties you are experiencing trying to map them is a symptom of the mismatch.
I think you should either:
1. Change the type of the ViewModel property to string and then do validation in your controller to ensure the string entered is actually a number or:
2. Change the control that the View renders to a control that will only allow a number to be entered via a custom control or Javascript validation (as #Qun Wang recommends)
Personally, I'd recommend option 1. That way the ViewModel is not dependent on the View implementation.
Could you do this in your PrepareViewDataForAddAction method?..
if (!ViewData.ModelState.IsValid)
{
ViewData["StartDate.Hour"] = "Error";
}
The other fields on the form will still populate based on the properties of the Gig object.
I think you need to do some basic client side validation first.
don't allow it to post to the server.