In ASP.net MVC:
How should/Can I pass Form data (From the View) to the Controller?
This is the way I am heading :
The Controller Index function is passing a ViewModel object to the View.
The ViewModel object contains a paginated list as well as some SelectLists.
_ The ViewModel object also contains a custom class I named theFilter. This class' purpose is to hold the Filter information Posted from the View via a Form.
I want the Index [AcceptVerbs(HttpVerbs.Post)] function to receive theFilter object populated with the form data, as well as the page number (as is it right now)
Here are snippets of my code:
The Controller/Index postback function:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(int? page, CaseFilter caseFilter)
{
const int pageSize = 10;
var cases = caseRepo.FindAllCases();
var paginatedCases = new PaginatedList<Case>(cases, page ?? 0, pageSize);
return View(new CaseIndexViewModel(paginatedCases, caseFilter));
}
The Filter Class:
public class CaseFilter
{
int iVolume_id = 0,
iSubject_id = 0;
public CaseFilter() {
}
public int Volume_id { get { return iVolume_id; } set { iVolume_id = value; } }
public int Subject_id { get { return iSubject_id; } set { iSubject_id = value; } }
}
And the ViewModel class:
public class CaseIndexViewModel
{
public PaginatedList<Case> PaginatedCases { get; private set; }
public CaseFilter CaseFilter { get; private set; }
public CaseIndexViewModel(PaginatedList<Case> paginatedCases, CaseFilter caseFilter)
{
PaginatedCases = paginatedCases;
CaseFilter = caseFilter;
}
}
Basically I am trying to avoid using Request.Form to populate the Filter class, at least not to use it within the Controller.
Any help, suggestions or disses are welcome!
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(FormCollection collection)
{
string valueFromNameTextBox = collection["name"];
}
You can index into this collection with the names of all the inputs on the form.
Rather than complicate my method signatures, I've taken to using the ValueProvider property and Try/UpdateModel in the Controller to retrieve form/route values unless the values are simple properties. On the other hand, I would probably also not make the filter part of the model for the View -- I tend to have a narrower conception of the model for the page, wanting it rather to be the business model rather that a model of all the data on the page -- and would simply pass the filter values via ViewData.
To expand BFree's answer, you can go through all the elements in the form by doing something like this:
foreach (string key in collection.keys) {
if (key.contains("blah"))
text1 = collection[key];
}
If it has too many elements for the key.contains if, it can get a bit ugly though, so beware ;).
Finally, I do not need to even use the Request Collection. The CaseFilter object is filled automatically as I set it as a parameter in
public ActionResult Index(int? page, CaseFilter caseFilter)
The code above works as it is.
Related
I am working on themes at present and the majority of work is going well am just at the phase where I am figuring out what view to present to the user.
This would be on a controller method so far I have.
public static class ThemeViewExtensions
{
public static string GetViewPath(RazorView view , string viewName = "Default")
{
var theme = "Default";
var themeViewPath = $"/Themes/{theme}{view.ViewPath}/{viewName}.cshtml";
return viewPath;
}
}
But for some reason I cannot access it here yes I have my using statement in
public IActionResult Index()
{
return View(this.GetViewPath());
}
What I want to happen is to be able to use it as above and be able to get the ViewPath and name that is being called for example /Controller/Action/ViewName whatever cshtml is that possible on a view?
I have a view model that is used to display a form on one view, and then is also used to represent the POST data to an action. The action then displays another view model that contains much of the same data from the first view model. However, the first view model has several "display only" properties that are also required on the second view model (for display only on the second view also).
I am wondering what the best way to pass this "display only" data to the second view would be. Currently, the best solution I have come up with is to have a bunch of hidden form fields that contain the display only property values, and then the model gets auto-populated for the action that handles the form POST. However, using hidden form fields seems very "hackish", and there seems like there should be a better solution to passing this data to another view The action doesn't need the display only information, it is only accessing it to populate the properties of the second view model that is passed to the second view.
Let me just explain my question with code, as what I am after is probably better understood through code than words.
Models:
public class SearchFilters
{
// ...
}
public class SearchResult
{
public int Id { get; set; }
public bool Selected { get; set; }
public string SomeDisplayValue1 { get; set; }
public string SomeDisplayValue2 { get; set; }
// ...
}
public class ResultsViewModel
{
public IList<SearchResult> Results { get; set; }
// ...
}
public class DoSomethingWithSelectedResultsViewModel
{
public IList<SearchResult> SelectedResults { get; set; }
public string SomeOtherProperty { get; set; }
// ...
}
Controller:
[HttpPost]
public ActionResult Results(SearchFilters filters)
{
ResultsViewModel results = new ResultsViewModel();
// ...
return new View(results);
}
[HttpPost]
public ActionResult DoSomethingWithSelectedResults(ResultsViewModel model)
{
// ...
return View(new DoSomethingWithSelectedResultsViewModel
{
SelectedResults = model.Results.Where(r => r.Selected).ToList(),
SomeOtherProperty = "...",
// ...
});
}
View: Results.cshtml
#model ResultsViewModel
#using (Html.BeginForm("DoSomethingWithSelectedResults", "Search"))
{
<table>
for (int i = 0; i < Model.Results.Count; i++)
{
<tr>
<td>
#Html.CheckBoxFor(m => Model.Results[i].Selected)
#* I would like to eliminate these hidden inputs *#
#Html.HiddenFor(m => Model.Results[i].Id)
#Html.HiddenFor(m => Model.Results[i].SomeDisplayValue1)
#Html.HiddenFor(m => Model.Results[i].SomeDisplayValue2)
</td>
<td>#Html.DisplayFor(m => Model.Results[i].SomeDisplayValue1)</td>
<td>#Html.DisplayFor(m => Model.Results[i].SomeDisplayValue2)</td>
<tr>
}
</table>
<button type="submit">Do Something With Selected Results</button>
}
As far as I know, one of the best way to pass data from View to another View through a Controller is to use ViewBag, ViewData or TempData. As an example, you can pass the data retrieved from View I as shown below:
TempData[DataToBePassed] = model.CustomData;
And then retrieve this data in View II similar to that:
#if(TempData[DataToBePassed] != null)
{
var dataFromFirstView = TempData[DataToBePassed];
}
For more information take a look at When to use ViewBag, ViewData, or TempData in ASP.NET MVC 3 applications.
You could put the model in the TempData property of the controller, that way it's automatically available in the next request.
More here
Found what I was looking for, I just hadn't worked with MVC enough yet to know about it. The Controller.UpdateModel method does exactly what I was looking for.
Example (using the code from the question):
[HttpPost]
public ActionResult DoSomethingWithSelectedResults()
{
// Load initial model data here, in this case I had simply cached the results in
// temp data in the previous action as suggested by Emeka Awagu.
ResultsViewModel model = (ResultsViewModel)TempData["results"];
// Call UpdateModel and let it do it's magic.
UpdateModel(model);
// ...
return View(new DoSomethingWithSelectedResultsViewModel
{
SelectedResults = model.Results.Where(r => r.Selected).ToList(),
SomeOtherProperty = "...",
// ...
});
}
Using this method I was able to eliminate all the hidden form fields and did not have to write any custom copy logic, since UpdateModel deals with it automatically.
Note: I did have to implement some custom model binders to get things to work correctly with dictionaries and collections (see here, here, and here).
I am developing an ASP.NET MVC application, populating dropdownlist to view working fine in get method. I am populating to view like this in my controller
[HttpGet]
public ActionResult CreateEmployeeFamilyDetails(EmployeeSuperClass employeeSuperClass, int i = 0)
{
employeeSuperClass.FamilyDetailsFields = new FamilyList();
employeeSuperClass.FamilyDetailsFields.familyMembersList.Insert(0, new EmployeeFamilyTable());
*employeeSuperClass.FamilyDetailsFields.employee_RelationTable = dt.GetRelations();*
*employeeSuperClass.FamilyDetailsFields.employee_BloodGroupTable = dt.GetBloodGroups();*
*employeeSuperClass.FamilyDetailsFields.employee_NationalityTable = dt.GetNationalities();*
return View("CreateEmployeeFamilyDetails", employeeSuperClass);
}
Please look into starred lines
In case if there is any errors in model am getting null reference
In post action method look like this
[HttpPost]
public ActionResult CreateEmployeeFamilyDetails(EmployeeSuperClass employeeSuperClass, string Command)
{
if (ModelState.IsValid)
{
return("some view");
}
else
{
return view(employeeSuperClass);
}
}
I know again we have to create instance to populate dropdownlist this is rubbish to do same again and again
Can anyone explain how to store dropdown list collection in view separately and post them also with model?
(note: employee_relationTable is IEnumerable collection and it is a relationtable type this table contains relation id and relationname fields and am using this table in this class like below
public IEnumerable<EmployeeRelationTable> employee_RelationTable { get; set; }
For rest also am using same approach
Can we post employee_RelationTable from view and how?
Please help and your help would be greatly appreciated
Assuming dt is a member field available to all methods, what you can do is DRY up the population of the DropDowns in the View Model in a separate method, e.g.
private void PopulateDropDownsOnViewModel(EmployeeSuperClass model)
{
model.FamilyDetailsFields = new FamilyList
{
employee_RelationTable = dt.GetRelations(),
employee_BloodGroupTable = dt.GetBloodGroups(),
employee_NationalityTable = dt.GetNationalities()
}
}
Which can be used in the Get:
[HttpGet]
public ActionResult CreateEmployeeFamilyDetails(EmployeeSuperClass employeeSuperClass, int i = 0)
{
PopulateDropDownsOnViewModel(employeeSuperClass);
return View("CreateEmployeeFamilyDetails", employeeSuperClass);
}
and in the Post (and any other controller actions which need the drop downs)
[HttpPost]
public ActionResult CreateEmployeeFamilyDetails(EmployeeSuperClass employeeSuperClass, string Command)
{
if (ModelState.IsValid)
{
return("some view");
}
else
{
PopulateDropDownsOnViewModel(employeeSuperClass);
return View(employeeSuperClass);
}
}
If the drop downs are static, you can also look at caching these to prevent wasted IO to the database.
But no, don't serialize the the data in the View somehow (remember WebForms ViewState?) or fetch data from the View directly - this violates the MVC paradigm - the controller is responsible for providing data for the View to render.
I can't figure out how to "customize" the rules for the [Required] attribute when I stick it to a custom typed property. Code looks like this:
public class MyProp
{
public Guid Id {get;set;}
public string Target {get;set;}
}
public class MyType : IValidatableObject
{
public string Name {get;set;}
public MyProp Value {get;set;}
private MyType()
{
this.Name = string.Empty;
this.Value = new MyProp { Id = Guid.Empty, Target = string.Empty };
}
public MyType(Guid id) : this()
{
this.Value.Id = id;
// Fill rest of data through magic
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(this.Value.Id == Guid.Empty)
yield return new ValidationResult("You must fill the property");
}
}
This model shows up in forms (through its own EditorTemplate) as a textbox with a button which allows for selection from a list (the backing data is a Dynamics CRM 2011 Environment, and this model is actually aimed to represent a lookup attribute).
public class MyModel
{
// Many props
[Required] // This one is enforced correctly
public string MyString {get;set;}
[Required] // This one isn't
public MyType MyData {get;set;}
public MyModel() { this.MyData = new MyType(); }
}
The resulting view shows the field (empty, of course). User can only input data by clicking the field and choosing from a list (a jquery dialog takes care of this, and it already works).
The IValidatableObject interface sounds promising but the code doesn't seem to be ever invoked.
In the controller, I'm simply doing
[HttpPost]
public ActionResult MyAction(FormCollection data)
{
if (!ModelState.IsValid) return View();
// magic: handle data
}
What am I missing ? I probably misunderstood the IValidatableObject interface usage ?
Your controller action should take the view model as parameter instead of weakly typed FormCollection which has absolutely no relation to your model (and its validation rules):
[HttpPost]
public ActionResult MyAction(MyModel model)
{
if (!ModelState.IsValid)
{
return View();
}
// magic: handle model
}
Now the default model binder is going to be invoked in order to bind the view model from the request and evaluate any validation logic you might have in this model.
How do you expect from your code, ASP.NET MVC, to ever know that you are working with this MyModel class? You absolutely never used it in your POST action, so you cannot expect to have any validation on it.
Once you start using view models you should forget about weakly typed collections such as FormCollection and start working with those view models.
I want to do a foreach using two viewbag thus in the view, but I get an error in the second foreach.
#foreach (var item in ViewBag.stages)
{
<div id="style101">
<h2><span> <strong>#item.NameStage</strong></span></h2>
</div>
foreach (var item2 in (ViewBag.actions.IdStage == item.IdStage ))
How I can do this?
Try to avoid using ViewBag / ViewData to pass data from your action methods to view. You must have realized now that this made your view a bit dirty. Use strongly typed view models.
Assume your view is to create something. So create a view model for that. Assuming you have multiple Actions in each stage.
public class CreateSomeThingVM
{
public string Title { set;get;}
public List<Stage> Stages { set;get;}
public CreateSomeThingVM()
{
Stages=new List<Stage>();
}
}
public class Stage
{
public int ID { set;get;}
public string StageName { set;get;}
public List<Action> Actions { set;get;}
public Stage()
{
Actions =new List<Action>();
}
}
public class Action
{
public int ID { set;get;}
public string ActionName { set;get;}
}
And in your Action method, get the data and set the properties.
public ActionResult Create()
{
var vm=new CreateSomeThingVM();
vm.Stages=GetListOfStagesFromSomeWhereWithItsActions();
return View(vm);
}
Assumuing GetListOfStagesFromSomeWhereWithItsActions method returns a list of Stage object with its proper Actions.(You should do your filtering here as needed to get the relevant actions for each stage).
Now in your View which is strongly typed to our CreateSomeThingVM view model, write some clean code
#model CreateSomeThingVM
#foreach(var stage in Model.Stages)
{
<h2>#stage.StageName</h2>
foreach(var action in Model.Actions)
{
<p>#action.ActionName</p>
}
}
It looks like the expression in your second loop after the in keyword evaluates to a boolean rather than an IEnumerable.
Did you mean to use .where() to filter an enumerable instead?
Perhaps:
Viewbag.actions.where( a => a.IdStage == item.IdStage)