Asp.Net MVC Dynamic Model Binding Prefix - asp.net

Is there any way of changing the binding prefix with a value which comes from the request parameters?
I have many nested search popups, and all of them shares the same ViewModel.
I can add a binding prefix to all the fields when requesting for the Search filters, but i don't know how can i make the [Bind(Prefix = "")] to work with values coming from the request parameters.
// get the search filters with the bindingPrefix we need
public ActionResult Search(string bindingPrefix)
{
ViewData.TemplateInfo.HtmlFieldPrefix = bindingPrefix;
SearchViewModel model = new SearchViewModel
{
BindingPrefix = bindingPrefix
};
return PartialView("_SearchFilters", model);
}
// post the search filters values
[HttpPost]
public ActionResult Search([Bind(Prefix = model.BindingPrefix)]SearchViewModel model)
{
}

I don't know why you would want to do this, but this should work.
In your form on the view, have a hidden value
#Html.Hidden("BindingPrefix", Model.BindingPrefix)
Modify your action to the following
[HttpPost]
public ActionResult Search(SearchViewModel model)
{
UpdateModel(model, model.BindingPrefix);
}

Related

ASP.NET MVC Redirect To Action does not render final View

I'm trying this code:-
If no query string supplied to the Index Method then render a Branch Locator View. When a Branch Id is selected in that View, post back to a Redirect To Route Result OR Action Result method and then redirect back to Index with a query string of the selected Branch Id.
I can run through the code successfully without and then with the query string.
I even run through the Index View and can see the Model working however, the Index View does not render, the Branch Selector View remains. Network developer tools shows the correct URL with query string correctly in place when doing the Redirect.
(NOTE: Both methods are on the same controller).
If I add the same query string directly in the Browser address bar it works fine!
I have this code:
[HttpGet]
public ActionResult Index()
{
var querystringbranchId = Request.QueryString["branchId"];
if(!string.IsNullOrEmpty(querystringId))
{
....do stuff like build a model using the branchId...
return View(Model);
}
return View("BranchSelector")
}
[HttpPost]
public RedirectToRouteResult BranchDetails(FormCollection formCollection)
{
var querystringBranchId = formCollection["BranchList"];
var branchId = int.Parse(querystringBranchId);
return RedirectToAction("Index", new { branchId });
}
Try using strongly typed model on the post, and specifying the param as an actual param - Using View models is going to be much better for you.
I have tested the below - It seemed to work as expected for me:
[HttpGet]
public ActionResult Index(int? branchId)
{
if (branchId.HasValue)
{
return View(branchId);
}
return View("BranchSelector");
}
[HttpPost]
public RedirectToRouteResult BranchDetails(MyModel myModel)
{
return RedirectToAction("Index", new { myModel.BranchId });
}
public class MyModel
{
public int BranchId { get; set; }
}
The View:
<div>
#using (Html.BeginForm("BranchDetails", "Home", FormMethod.Post))
{
#Html.TextBox("BranchId","123")
<input type="submit" value="Go"/>
}
</div>
#MichaelLake Thanks to your post I found the problem. I tried your code and sure enough it works as expected. I didn't mention I was using a Kendo Combobox control (!) loaded with the branches. I didn't mention that as the actual data I needed was available in the post method so, thought the issue was with the Controller methods. I had the Kendo control name as BranchList, I changed it to BranchId and it now works with the original code as expected! The Kendo name becomes the element Id and has to match to work.
Many Thanks!
This will work for you. Cheers :D
return RedirectToAction("Index", "ControllerName", new { branchId = branchId});

How to display view model validation for a view that has multiple forms on it?

I'm trying to better understand how to properly structure my ASP.NET MVC code to handle a situation where a single view contains multiple forms. I feel that it makes sense to submit the forms to their own action methods, so that each form can benefit from its own view model parameter binding and validation, and to avoid putting all form parameters into 1 larger, monolithic view model.
I'm trying to code this pattern, but I can't seem to tie the loose ends together.
I've written some example action methods below, along with example view model classes, that I think demonstrate what I'm trying to achieve. Lets say that I've got an Item Detail action method and view. On this Detail view, I've got two forms - one that creates a new Comment and another that creates a new Note. Both Comment and Note forms POST to their own action methods - DetailNewComment and DetailNewNote.
On success, these POST handler action methods work just fine. On an invalid model state though, I return View(model) so that I can display the issues on the original Detail view. This tries to render a view named Brief though, instead of Detail. If I use the overloaded View call that allows me to specify which view to render, then now I have issues with the different view model classes that I'm using. The specific view model classes now no longer work with the original DetailViewModel.
I get the feeling that I'm doing this completely wrong. How am I supposed to be handling this scenario with multiple forms? Thanks!
public ActionResult Detail(int id)
{
var model = new ItemDetailViewModel
{
Item = ItemRepository.Get(id)
};
return View(model);
}
[HttpPost]
public ActionResult DetailNewComment(int id, ItemDetailNewCommentViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var comment = CommentRepository.Insert(new Comment
{
Text = model.Text
});
return RedirecToAction("Detail", new { id = id; });
}
[HttpPost]
public ActionResult DetailNewNote(int id, ItemDetailNewNoteViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var note = NoteRepository.Insert(new Note
{
Text = model.Text
});
return RedirectToAction("Detail", new { id = id; });
}
... with view models something like ...
public class ItemDetailViewModel
{
public Item Item { get; set; }
}
public class ItemDetailNewCommentViewModel
{
public string Text { get; set; }
}
public class ItemDetailNewNoteViewModel
{
public string Text { get; set; }
}
For your case I'd recommend to have a master model for example your
ItemDetailViewModel class to which you'll add a property for each sub-model
public class ItemDetailViewModel
{
public Item Item { get; set; }
public ItemDetailNewCommentViewModel NewCommentModel {get;set;}
public ItemDetailNewNoteViewModel NoteModel {get;set;}
}
Your Detail view will be the master view and the other two will be partial views.
Master view will receive an instance of ItemDetailViewModel as model and inside view you will render your partials by passing Model.NewCommentModel and Model.NoteModel as their corresponding models. For being able to use separate actions for each form, instead of regular forms you can use ajax forms, thus you will send to the server only relevant information without altering the rest of the master view.
The chief problem here is what happens when the user messes up and their post doesn't pass validation server-side. If you choose to take them to a page where just the one form is presented, then you can post to a different action, but if you want both forms re-displayed, then they both should point to the same action.
Really, you just have to make a choice. I've seen sites handle it both ways. Personally, I prefer to re-display the original form, which means handling both forms in the same action. It can lead to bloat, but you can factor out a lot of logic from the action such that you end up with mostly just a branch depending on which form was submitted.

MVC3 Html editor helpers display old model value

After form submit Html editor helpers (TextBox, Editor, TextArea) display old value not a current value of model.text
Display helpers (Display, DisplayText) display proper value.
Is there any way editor helpers to display current model.text value?
Model
namespace TestProject.Models
{
public class FormField
{
public string text { get;set; }
}
}
Controller
using System.Web.Mvc;
namespace TestProject.Controllers
{
public class FormFieldController : Controller
{
public ActionResult Index (Models.FormField model=null)
{
model.text += "_changed";
return View(model);
}
}
}
View
#model TestProject.Models.FormField
#using (Html.BeginForm()){
<div>
#Html.DisplayFor(m => m.text)
</div>
<div>
#Html.TextBoxFor(m => m.text)
</div>
<input type="submit" />
}
When you submit the form to an MVC action the values of the input fields are recovered from the POSTEd values available in the form and not from the model. That makes sense right? We don't want the user to show a different value in a textbox than they have just entered and submitted to the server.
If you want to show the updated model to the user then you should have another action and from the post action you have to redirect to that action.
Basically you should have two actions one action that renders the view to edit the model and another one saves the model to database or whatever and redirect the request to the former action.
An example:
public class FormFieldController : Controller
{
// action returns a view to edit the model
public ActionResult Edit(int id)
{
var model = .. get from db based on id
return View(model);
}
// action saves the updated model and redirects to the above action
// again for editing the model
[HttpPost]
public ActionResult Edit(SomeModel model)
{
// save to db
return RedirectToAction("Edit");
}
}
When using HTML editors such as HTML.EditorFor() or HTML.DisplayFor(), if you attempt to modify or change the model values in the controller action you won't see any change unless you remove the ModelState for the model property you want to change.
While #Mark is correct, you don't have to have a separate controller action (but you usually would want to) and you don't need to redirect to the original action.
e.g. - call ModelState.Remove(modelPropertyName)...
public ActionResult Index (Models.FormField model=null)
{
ModelState.Remove("text");
model.text += "_changed";
return View(model);
}
And if you want to have separate actions for GET and POST (recommended) you can do...
public ActionResult Index ()
{
Models.FormField model = new Models.FormField(); // or get from database etc.
// set up your model defaults, etc. here if needed
return View(model);
}
[HttpPost] // Attribute means this action method will be used when the form is posted
public ActionResult Index (Models.FormField model)
{
// Validate your model etc. here if needed ...
ModelState.Remove("text"); // Remove the ModelState so that Html Editors etc. will update
model.text += "_changed"; // Make any changes we want
return View(model);
}
I had some similar problem, I hope I can help others have similar problem:
ActionExecutingContext has Controller.ViewData.
as you can see:
new ActionExecutingContext().Controller.ViewData
This ViewData contains ModelState and Model. The ModelState shows the state of model has passed to controller for example. When you have an error on ModelState the unacceptable Model and its state passed to View. So you will see the old value, yet. Then you have to change the Model value of ModelState manually.
for example for clearing a data:
ModelState.SetModelValue("MyDateTime", new ValueProviderResult("", "", CultureInfo.CurrentCulture));
Also you can manipulate the ViewData, as here.
The EditorFor, DisplayFor() and etc, use this ViewData contents.

How to get the updated contents in a YUI simple editor from your view model

I am using ASP.NET MVC3 with the razor view engine. I am also using a the Yahoo User Interface 2 (YUI2) simple editor.
My view has a view model called ProductEditViewModel. In this view model I have a property defined as:
public string LongDescription { get; set; }
In my view I would create the YUI2 simple editor from this input field. The field is defined in the view like:
<td>#Html.TextAreaFor(x => x.LongDescription, new { cols = "75", rows = "10" })<br>
#Html.ValidationMessageFor(x => x.LongDescription)
</td>
Here is a partial view of my Edit action method:
[Authorize]
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(ProductEditViewModel viewModel)
{
if (!ModelState.IsValid)
{
// Check if valid
}
// I added this as a test to see what is returned
string longDescription = viewModel.LongDescription;
// Mapping
Product product = new Product();
product.InjectFrom(viewModel);
// Update product in database
productService.Update(product);
return RedirectToRoute(Url.AdministrationProductIndex());
}
When I view the contents of the longDescription variable then it should contain the values from the editor. If I edit the contents in the editor then longDescription still only contains the original contents, not the updated contents. Why is this?
I suspect that somewhere in your POST action you have written something like this:
[Authorize]
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(ProductEditViewModel viewModel)
{
...
viewModel.LongDescription = "some new contents";
return View(viewModel);
}
If this is the case then you should make sure that you have cleared the value from the ModelState before modifying it because HTML helpers will always first use the value from model state and then from the model.
So everytime you intend to manually modify some property of your view model inside a POST action make sure you remove it from modelstate:
ModelState.Remove("LongDescription");
viewModel.LongDescription = "some new contents";
return View(viewModel);
Now when the view is displayed, HTML helpers that depend on the LongDescription property will pick the new value instead of using the one that was initially submitted by the user.

MVC2 and two different models using same controller method? Possible?

I don't know if this is the right way of doing this or not, but I am using Jquery and MVC2. I am using a the $.ajax method to make a call back to a controller to do some business logic on a .blur of a textbox.
I have two views that basically do the same thing with the common data, but are using different models. They both use the same controller. It might be easier to explain with code:
So here are the two models:
public class RecordModel {
public string RecordID { get; set; }
public string OtherProperties { get; set; }
}
public class SecondaryModel {
public string RecordID { get; set; }
public string OtherPropertiesDifferentThanOtherModel { get; set; }
}
There are two views that are strongly typed to these models. One is RecordModel, the other SecondaryModel.
Now on these views is a input="text" that is created via:
<%= Html.TextBoxFor(model => model.RecordID) %>
There is jQuery javascript that binds the .blur method to a call:
<script>
$('#RecordID').blur(function() {
var data = new Object();
data.RecordID = $('#RecordID').val();
// Any other stuff needed
$.ajax({
url: '/Controller/ValidateRecordID',
type: 'post',
dataType: 'json',
data: data,
success: function(result) {
alert('success: ' + result);
},
error: function(result) {
alert('failed');
}
});
}
</script>
The controller looks like:
[HttpPost]
public ActionResult ValidateRecordID(RecordModel model) {
// TODO: Do some verification code here
return this.Json("Validated.");
}
Now this works fine if I explicitly name the RecordModel in the controller for the View that uses the RecordModel. However, the SecondaryModel view also tries to call this function, and it fails because it's expecting the RecordModel and not the SecondaryModel.
So my question is this. How can two different strongly typed views use the same Action in a controller and still adhering to the modeling pattern? I've tried abstract classes and interfaces (and changing the view pages to use the Interface/abstract class) and it still fails.
Any help? And sorry for the robustness of the post...
Thanks.
You could define an interface for those classes.
interface IRecord
{
string RecordID { get; set; }
string OtherProperties { get; set; }
}
and make the method receive the model by using that:
[HttpPost]
public ActionResult ValidateRecordID(IRecord model)
{
// TODO: Do some verification code here
return this.Json("Validated.");
}
If you only need the RecordID, you can just have the controller method take int RecordID and it will pull that out of the form post data instead of building the view model back up and providing that to your action method.
[HttpPost]
public ActionResult ValidateRecordID(int RecordID) {
// TODO: Do some verification code here
return this.Json("Validated.");
}
There is no direct way of binding data to a interface/abstract class. The DefaultModelBinder will try to instantiate that type, which is (by definition) impossible.
So, IMHO, you should not use that option. And if you still want to share the same controller action between the two views, the usual way of doing that would be using a ViewModel.
Make your strongly-typed views reference that viewmodel. Make the single shared action receive an instance of it. Inside the action, you will decide which "real" model should be used...
If you need some parameter in order to distinguish where the post came from (view 1 or 2), just add that parameter to the ajax call URL.
Of course, another way is keeping what you have already tried (interface/abstract class), but you'll need a custom Model Binder in that case... Sounds like overcoding to me, but it's your choice.
Edit After my dear SO fellow #Charles Boyung made a gracious (and wrong) comment below, I've come to the conclusion that my answer was not exactly accurate. So I have fixed some of the terminology that I've used here - hope it is clearer now.
In the case above your action could accept two strings instead of a concrete type.
Another possibility is having two actions. Each action taking one of your types. I'm assuming that functionality each type is basically the same. Once the values have been extracted hand them off to a method. In your case method will probably be the same for each action.
public ActionResult Method1(Record record)
{
ProcessAction(record.id, record.Property);
}
public ActionResult Action2(OtherRecord record)
{
ProcessAction(record.id, record.OtherProperty);
}
private void ProcessAction(string id, string otherproperity)
{
//make happen
}

Resources