So I've got a simple model called BaseModel that has properties all models need. Like for example my Provider class.
public Provider Provider { get; set; }
And I have a SearchModel that derives from BaseModel. It just has an extra string and list.
Now in my html file I have this:
In the top I declared my model:
#model SearchModel
And in my page, I have this form.
#using (Html.BeginForm("Search", "Home", FormMethod.Post))
{
#Html.EditorFor(model => model.SearchText)
#Html.HiddenFor(model => model.Provider)
<button id="BtnSearch" type="submit">Search</button>
}
And finally this is my controller:
public ActionResult Search(SearchModel model)
{
Session["Provider"] = model.Provider;
if (!string.IsNullOrEmpty(model.SearchText)) model.Search();
return View(model);
}
Only what seems to happen is, Provider is null when it reaches my POST action. The weird thing is, it worked in the past and I don't recall changing anything.
Related
I have a controller sending a view model consisting of a list and an object client to a view.
The view will show the list in a grid but hide the object client.
Here is the view:
#model .Business.BusinessModels.MatchesClientViewModel
#{
ViewBag.Title = "SaveClient";
}
<h2>SaveClient</h2>
<h3>
The info captured matches #Model.ClientMatches.Count()
</h3>
#using (Html.BeginForm("SaveClient", "Client", FormMethod.Post))
{
#Html.AntiForgeryToken()
WebGrid grid = new WebGrid(Model.ClientMatches);
#grid.GetHtml(columns: new[]
{
grid.Column("Name"),
grid.Column("Surname"),
grid.Column("Email"),
})
#Html.Hidden("client", Model.client)
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
My action which submit button hits is:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SaveClient(MatchesClientViewModel matchesClientViewModel)
{
*some actions*
return View();
}
However the view model sent to controller from view is null. Any idea how to properly pass the hidden part of the view model to the controller?
If you want to pass an entire object as hidden you will have to add hidden fields for every attribute of the class like this:
Let's say the model Client is something like this
public class Client
{
public string Id { get; set; }
public string SomeAttribute { get; set; }
\\ ......
}
In order to pass your values to your Action you should add to your form every property as a hidden field like this
#Html.HiddenFor(m => m.client.Id, Model.client.Id)
#Html.HiddenFor(m => m.client.Someattribute, Model.client.Someattribute)
One other way to go would be to change your model to something like this:
public class MatchesClientViewModel
{
public List<ClientMatch> ClientMatches { get; set; }
public string clientId { get; set; }
\\ .....
}
and pass your clientId only to your view and back to the controller like this
#Html.HiddenFor(m => m.clientId., Model.clientId)
Or If you do not want to change your ViewModel just add a hidden field like you did for your object for the client id and pass it as an extra parameter to the controller
#Html.HiddenFor('clientId', Model.client.Id)
and make your action like this
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SaveClient(MatchesClientViewModel matchesClientViewModel, string clientId)
{
*some actions*
return View();
}
My Model;
namespace GeneralUtility.Models
{
public class MyModel
{
public int BirthDateYear { get; set; }
public String Details { get; set; }
}
}
My Controller
namespace GeneralUtility.Controllers
{
public class WorkspaceController : Controller
{
public ActionResult MyHelper(MyModel model)
{
if(model.someCondition)
model.Details= "TEST";
else
model.Details= "Some other TEST";
return View(model);
}
}
}
My View
<div data-role="fieldcontain">
#using (Html.BeginForm("MyHelper", "WorkSpace", FormMethod.Post, new { id = "frmMyForm" }))
{
...
<div data-role="fieldcontain">
#Html.EditorFor(x => x.Details )
</div>
...
}
</div>
After I submit my form, I can see the model and any changes I make in MyHelper action method. However, when I make changes and return the model from the controller (I can see the changes in the model while in debug). I get the same value as before in #Html.EditorFor(x => x.Details ) field. What can I do to get the changed Details value of the model to show?
That's because by default ASP.NET MVC returns the model that was passed into a method (using what is stored in the ModelState, if it exists). If it doesn't exist, it uses what you pass to the View.
In order to prevent this, you need to call ModelState.Clear() before returning your view.
Check this blog post for a more detailed explanation.
I've been working on an MVC 4 Application and have run into a problem when attempting to update Models in a ViewModel.
My ViewModel (detailed below) contains one ComplexObjectOne and a List<ComplexObjectTwo>.
My GET ActionResult successfully populates the ViewModel from a database and everything displays correctly on my View.
The problem is encountered when attempting to pass the ComplexObjectOne and List<ComplexObjectTwo> to the POST ActionResult.
The ComplexObject is passed correctly but everything I've tried fails pass the List<ComplexObjectTwo> collection.
My ComplexModelOne Model
public class Test
{
public int Id {get;set;}
public string Result {get;set;}
public virtual ICollection<TestResult> TestResults {get;set;}
}
My ComplexModelTwo Model
public class TestResult
{
public int Id {get;set;}
public string Result {get;set;}
public string Comment {get;set;}
public virtual Test Test{get;set;}
}
My ViewModel
public class TestingViewModel
{
public TestingViewModel()
{
if(TestResults == null)
{
TestResults = new List<TestResult>();
}
}
public Test Test {get;set;}
public IEnumerable<TestResult> TestResults {get;set;}
}
My Edit() GET ActionResult
public ActionResult Edit(int id = 0)
{
var viewModel = new TestingViewModel();
Test test = testRepo.GetTestById(id);
var results = test.TestResults;
viewModel.Test = test;
viewModel.TestResults = results;
return View(viewModel);
}
My Edit() POST ActionResult
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(TestingViewModel model)
{
// do update - left out for brevity
}
My Edit.cshtml View
#model Namespace.Models.ViewModels.TestingViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.EditorFor(model => model.Test, "TestHeader")
<table>
<tr>
<th>Test</th>
<th>Result</th>
<th>Comment</th>
</tr>
#Html.EditorFor(model => model.TestResults, "TestResults")
</table>
<input type="submit" value="Update"/>
}
Within my View I do use a couple of EditorTemplates to display the property fields.
Any assistance, comments, or suggestions will be much appreciated. I'd like to be able to accomplish updating these entities on a single page instead of multiple pages which I resorted to in the Create() steps.
Thank you,
Patrick H. (stpatrck)
Replace:
#Html.EditorFor(model => model.TestResults, "TestResults")
with:
#Html.EditorFor(model => model.TestResults)
and then rename your EditorTemplates/TestResults.cshtml editor template to EditorTemplates/TestResult.cshtml (notice the missing s) and inside replace the model declaration from:
#model IEnumerable<TestResult>
to:
#model TestResult
Now obviously this will lead to getting rid of any for or foreach loops you might have written in this editor template because now ASP.NET MVC will automatically invoke the template for each element of the collection.
So for example:
#foreach (var item in Model)
{
#Html.EditorFor(x => item.SomeProperty)
}
will simply become:
#Html.EditorFor(x => x.SomeProperty)
Now look at the generated markup and notice the difference in the names of your input fields. Before you had:
<input type="text" name="item.SomeProperty" value="foo" />
and now you have:
<input type="text" name="TestResults[0].SomeProperty" value="foo" />
Now when you submit the form to the POST action the default model binder will be able to successfully bind the collection because now the naming convention is respected. You can read more about this convention in the following blog post.
Also you have circular references in your object graph which cannot be successfully serialized and model bound. You should use view models in order to break this circular dependency.
I'm trying to display two partial views in my index view. In those partial views are data grids that I want to display data when something is searched in the search box I have set up. Both of these pages work when I do them separately, but I don't know how do use them as partial views.
My View looks like this:
#using (Html.BeginForm("Index", "Home", "POST"))
{
<div class="searchField">
<div class="searchbox">
Search: <input type="text" name="heatSearch" />
<input type="submit" value="Submit">
</div>
</div>
}
<div>
#Html.Partial("PartialChemAnalysis", (string)ViewBag.SearchKey)
</div>
#Html.Partial("PartialSlag", (string)ViewBag.SearchKey)
My Controller looks like this:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(string heatSearch)
{
ViewBag.SearchKey = heatSearch;
return View();
}
public ActionResult PartialChemAnalysis(string heatSearch)
{
HomeModel C = new HomeModel();
IEnumerable<HomeModel> model = C.ChemList;
C.ChemistryDataPull(heatSearch);
return PartialView(C.ChemList);
}
public ActionResult PartialSlagView(string heatSearch)
{
PartialSlagViewModel D = new PartialSlagViewModel();
IEnumerable<PartialSlagViewModel> model = D.SlagList;
D.SlagViewDataPull(heatSearch);
return PartialView(D.SlagList);
}
Ideally what's in that search box would be passed to both views and the grids would form based on that. I'm not sure what I'm doing wrong so any help is appreciated.
I would start with this:
#{
//create your first model
HomeModel CModel = new HomeModel();
CModel.ChemistryDataPull(Model.SearchValue);
//create your other model
PartialSlagViewModel DModel = new PartialSlagViewModel();
DModel.SlagViewDataPull(Model.SearchValue);
}
#Html.Partial("PartialAnalysis", CModel)
#Html.Partial("PartialSlag", DModel)
This is assuming you've already searched, processed a postback, and you've returned the SearchValue back to your view in your model. You could return it in ViewBag.SearchValue instead I suppose and replace Model.SearchValue with ViewBag.SearchValue, but your model would be a better place to store it.
If I were you, I post it to another method.
#using (Html.BeginForm("Index", "Home", "POST"))
{
<div class="searchField">
<div class="searchbox">
Search: <input type="text" name="Search" />
<input type="submit" value="Submit">
</div>
</div>
}
#Html.Partial("PartialAnalysis", (string)ViewBag.SearchKey)
#Html.Partial("PartialSlag", (string)ViewBag.SearchKey)
//In Home Controller
[HttpPost]
public ActionResult Index(string Search)
{
ViewBag.SearchKey = Search;
return View();
}
Partial views do not require a controller action. The presence of a controller action will actually make it into a view.
In your #Html.Partial call you want to pass a view model for the view to consume. This is where you'd want to put the data for your grid, searched by whatever keywords, sorted, prepped and ready to render.
This can either be an entirely different view model you've created that is dedicated to supporting your partial view, exposed as a property in the parent page's view model, or simply an IEnumerable property in the parent page's view model that has the data (I prefer the first approach btw; it's heavier code-wise but preserves encapsulation better).
To wrap it all up, your controller looks like:
public class HomeController : Controller
{
public ActionResult Index(string search)
{
return View(new IndexViewModel(search));
}
}
Your view model looks like:
public class IndexViewModel
{
private string _search;
public IndexViewModel(string search)
{
_search = search;
}
public AnalysisViewModel AnalysisViewModel
{
get
{
return new AnalysisViewModel(_search);
}
}
public SlagViewModel SlagViewModel
{
get
{
return new SlagViewModel(_search);
}
}
}
and your view for showing the partials looks like
#Html.Partial("PartialAnalysis", Model.AnalysisViewModel)
#Html.Partial("PartialSlag", Model.SlagViewModel)
I needed to change my partial view calls in my view to:
#if(ViewBag.SearchKey != null)
{
<div>
#Html.Action("PartialChemAnalysis", "Home", (string)ViewBag.SearchKey)
</div>
<div>
#Html.Action("PartialSlagView", "Home", (string)ViewBag.SearchKey)
</div>
}
I have a class that looks like this:
public class UserListVM
{
public SearchModel SearchModel { get; set; }
public PagedList<User> Users { get; set; }
}
public class SearchModel
{
public string Text { get; set; }
/* other properties */
}
I send UserListVM to my view but the action accepts SearchModel:
public ActionResult Search(SearchModel filter)
{
UserListVM model = new UserListVM();
model.Users = userService.GetUsers(filter);
model.SearchModel = filter;
return View(model);
}
My view is:
#model UserListVM
<form>
#Html.TextBoxFor(m => Model.SearchModel.Text)
</form>
But this generates:
<input id="SearchModel_Text" name="SearchModel.Text" type="text" value="">
Which sends UserListVM to the action instead of SearchModel. How can I get it to generate this:
<input id="Text" name="Text" type="text" value="">
#Html.TextBoxFor(m => m.SearchModel.Text, new { id = "Text" })
Utilize the overloaded TextBoxFor() method that takes a second object parameter (called htmlAttributes). Here you can specify HTML attributes to apply to the DOM element you are currently utilizing (in this case, your input element).
Edit: I believe your lambda expression is wrong. Change:
#Html.TextBoxFor(m => Model.SearchModel.Text)
To
#Html.TextBoxFor(m => m.SearchModel.Text)
// htmlAttributes omitted to show the issue
Edit Edit: it turns out that even with a specified name attribute, it will be rendered according to what the form is requiring for a POST to the necessary field(s).
Edit Edit Edit: Try to be explicit with FormExtensions.BeginForm():
#using (Html.BeginForm("Search", "YourController", FormMethod.Post, null))
{
#Html.TextBoxFor(m => m.SearchModel.Text)
}
Use this as a substite of your <form /> element.
Create a partial view for your SearchModel, and call it using Html.Partial. Then, from within that partial view, do all of the EditorFor/TextBoxFor Extensions
Your view - UserList.cshtml:
#model UserListVM
#using (Html.BeginForm())
{
#Html.Partial("Search", Model.SearchModel)
}
Your view - Search.cshtml:
#model SearchModel
#Html.TextAreaFor(m => m.Text)
Assumming there is more the the View than you have shown, why not just have your Search method take the UserListVM model. It will just contain a null reference to the users, so there is no extra data sent in the post.
Try doing it manually like this:
#Html.TextBox("Text", Model.SearchModel.Text)