I have a list of teams on my index page.
I'm trying to pass the text of an input(type text) from the index view back to the index controller, to reload the index page, this time only displaying items in my list which have matching text. eg - bob = bob
Index Controller
public ActionResult Index(string searchString)
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
var listOfTeams = from T in db.Teams
select T;
if (!String.IsNullOrEmpty(searchString))
{
listOfTeams = listOfTeams.Where(T => T.TeamName.Contains(searchString));
}
return View(listOfTeams.ToList());
}
How i'm trying to pass the data in the Index view
I've tried
<input type="text" id="inputTeamSearch" name="searchString" class="form-control" style="width:225px;height:60px" onblur="IsTextEmpty()" oninput="CheckTeams()" placeholder="Search">
#Html.ActionLink("Search", "Index")
and
#using(Html.BeginForm("Index", "Team"))
{
<input type="text" id="inputTeamSearch" name="searchString" class="form-control" style="width:225px;height:60px" onblur="IsTextEmpty()" oninput="CheckTeams()" placeholder="Search">
<input type="submit" id="Index" value="Index" />
Html.EndForm();
}
I'm sure this is probably a duplicate of some sort, if so please just pass me in the appropriate direction. I've looked for answers, but they're either long-winded or go into more complex detail than this.
So to post data to a controller you need a seperate post action which is decorated with the HttpPost attribute. This method needs to take a model as it's parameter:
[HttpPost]
Public ActionResult Index(IndexVM model)
{
var searchTerm = model.SearchTerm;
}
The view model needs to contain the fields that you intend to post.
Public class IndexVM
{
Public String SearchTerm { get; set; }
//Other model examples
public Int32 PageNumber { get; set; }
public Int32 NumOfItemsPerPage { get; set; }
}
Then your html needs to contain a text box that has the same name as the string property in your view model.
#Html.TextBoxFor(m => m.SearchTerm)
//And at the top of your html page you will need to include the model
#model Domain.Models.IndexVM
OR
<input type="text" name="SearchTerm">
Should work.
If you are already using an entity model you can create a new View model which contains the old entity and whatever else you need. So:
public class IndexVM
{
public Team Team { get; set; }
public String SearchTerm { get; set; }
}
Then on your index GET method where you're passing your team to your view you would have:
var view = new IndexVM();
view.Team = //your team object
return View(view);
Related
I'm trying to get simple ASP.NET CORE web app going and I'm running into issues with allowing the user to access a file on my server via a html link. I have a model called "TestModel", a view called "TestView" and a controller called "AppController". My view allows the user to input some text in two separate fields as well as select two different files from their hard drive, it binds everything to my model and performs a POST when the user clicks a button. I have verified that the model is correctly being passed back to my controller. My controller correctly saves the files to a folder in my server directory, uses a separate service to manipulate the files, and returns the model back to the view, i.e. when I use the debugger to inspect "return View(model)" in the Testview action the model being passed back has all it's properties populated.
The issue is that I want two links on the view to point to the files on the server so that the user can click the link and receive a prompt to download the files, but I can't seem to get it going. I am using the #Html.ActionLink() capability in razor to point to a "Download descriptions" action and pass the model to it, thinking that it would be passing the current view's model, but the model it passes has all fields as null. Any insight into what I am doing incorrectly?
These are the relevant actions in my AppController:
[HttpPost("~/App/TestView")]
public IActionResult TestView(TestModel Model)
{
if (ModelState.IsValid)
{
Console.WriteLine("Something was sent back from the TestView page");
Model.OnPostAsync();
var x = _descLogicService.Convert(Model);
}
return View(Model);
}
public IActionResult DownloadDescriptions(TestModel model)
{
var cd = new ContentDispositionHeaderValue("attachment")
{
FileNameStar = model.DescriptionFile.FileName
};
Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
byte[] bytes = System.IO.File.ReadAllBytes(model.MeasurementExportFile);
using (FileStream fs = new FileStream(model.MeasurementExportFile, FileMode.Open, FileAccess.Read))
{
fs.Read(bytes, 0, System.Convert.ToInt32(fs.Length));
fs.Close();
}
return File(bytes, "text/csv");
}
Here is my View Model:
public class TestModel
{
[BindProperty]
[Required]
[MinLength(5)]
public string Name { get; set; }
[BindProperty]
[Required]
[MaxLength(10,ErrorMessage ="Input string is too long.")]
public string MyInput { get; set; }
[BindProperty]
[Required]
public IFormFile DescriptionFile { get; set; }
[BindProperty]
[Required]
public IFormFile MeasurementFile { get; set; }
public string DescriptionDirectory { get; set; }
public string DescriptionExportFile { get; set; }
public string MeasurementDirectory { get; set; }
public string MeasurementExportFile { get; set; }
public async Task OnPostAsync()
{
var token = DateTime.Now.Ticks.ToString();
var directory = Directory.CreateDirectory(#"uploads\"+ token + #"\");
DescriptionDirectory = directory.CreateSubdirectory("Descriptions").FullName + #"\";
DescriptionExportFile = directory.CreateSubdirectory(#"Descriptions\Exports\").FullName + DescriptionFile.FileName;
MeasurementDirectory = directory.CreateSubdirectory("Measurements").FullName + #"\";
MeasurementExportFile = directory.CreateSubdirectory(#"Measurements\Exports\").FullName + MeasurementFile.FileName;
var file = Path.Combine(DescriptionDirectory, DescriptionFile.FileName);
using (var fileStream = new FileStream(file, FileMode.Create))
{
await DescriptionFile.CopyToAsync(fileStream).ConfigureAwait(true);
}
file = Path.Combine(MeasurementDirectory, MeasurementFile.FileName);
using (var fileStream = new FileStream(file, FileMode.Create))
{
await MeasurementFile.CopyToAsync(fileStream).ConfigureAwait(true);
}
}
}
And here is the View:
#**I need to add the namespace of C# models I'm creating *#
#using FirstASPNETCOREProject.ViewModels
#*I need to identify the model which 'fits' this page, that is the properties of the model can be
bound to entities on the view page, using "asp-for"*#
#model TestModel
#{
ViewData["Title"] = "Page for File Uploads";
}
#section Scripts{
}
<div asp-validation-summary="ModelOnly" style="color:white"></div>
<form method="post" enctype="multipart/form-data">
<label>Enter a Description File Name</label>
<input asp-for="Name" type="text" />
<span asp-validation-for="Name"></span>
<br>
<label>Select a Description File</label>
<input asp-for="DescriptionFile" type="file" />
<span asp-validation-for="DescriptionFile"></span>
<br>
<label>Enter the Measurement File Name</label>
<input asp-for="MyInput" type="text">
<span asp-validation-for="MyInput"></span>
<br>
<label>Select a Measurement File</label>
<input asp-for="MeasurementFile" type="file">
<span asp-validation-for="MeasurementFile"></span>
<br>
<input type="submit" value="Send Message" />
</form>
#Html.ActionLink("Description File", "DownloadDescriptions", "App")
#Html.ActionLink("Measurement File", "DownloadMeasurements", "App")
ActionLinks are just anchor tags.
So they use GET. So to pass back your model using get you would need to use Query String parameters for example adding "?MyInpu=some cool input" at the end of the url.
You can bind almost ANY complex object like this including Lists and Arrays.
For more information on Model Binding
The file itself you wont be able to pass it like that. For that you will need to POST the form with a submit button or FormData using javascript.
You can also add anchors that call javascript functions that use AJAX to post back all you want to the DownloadDescriptions action in your controller.
Here is an example on how to pass a model using an action link:
#Html.ActionLink("Test Action", "TestAction", "Home", new { myInput = "Some Cool Input" })
In my case the previous ActionLink produces an anchor tag with href set to:
http://localhost:64941/Home/TestAction?myInput=Some Cool Input
Notice how I used an anonymous type to pass the model using the same names of the properties of my model in this case MyInput but in camelized version myInput.
You can compose any model like that.
This is my action in my controller:
public IActionResult TestAction([FromQuery]TestModel input)
{
return View(input);
}
Notice how I used [FromQuery] for the TestModel parameter to indicate that I expect the ASP.NET Core model binder to use the Query String parameters to populate my model.
This is my model class:
public class TestModel
{
public string MyInput { get; set; }
}
This is the result during debugging:
Notice how during debugging I am able to see the populated value.
NOTES:
If your model changes at the client side. You will need to update the Query String parameters in the anchor tag using javascript... for that reason is a good idea to add name to the anchor tag.
Also this might answer your question but might NOT be the best approach to what you are trying to do.
I have an a href link to a page which adds a parameter to the link for example:
tsw/register-your-interest?Course=979
What I am trying to do is to extract the value in Course i.e 979 and display it in the view. When attempting with the below code, I only return with 0 rather than the course value expected. ideally I'd like to avoid using routes.
Here is the view:
<div class="contact" data-component="components/checkout">
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Model.Course;
}
And my controller:
public ActionResult CourseEnquiry(string Course)
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
model.Course = Request.QueryString["Course"];
return model
}
This is the View Model:
public class CourseEnquiryVM : PageContentVM
{
public List<OfficeLocation> OfficeLocations { get; set; }
public string Test { get; set; }
public string Course { get; set; }
public List<Source> SourceTypes { get; set; }
}
SOLUTION:
After some research and comments I've adjusted the code to the below which now retrieves the value as expected
#Html.HiddenFor(m => m.Course, new { Value = #HttpContext.Current.Request.QueryString["Course"]});
Thanks all
Based on the form code you provided you need to use #Html.HiddenFor(m => m.Course) instead of just #Model.Course. #Model.Course just displays the value as text instead of building a input element that will be sent back to your controller.
If your problem is with a link prior to the view you referenced above, here's what I'd expect to work:
View with link:
#model CourseEnquiryVM
#Html.ActionLink("MyLink","CourseEnquiry","CourseController", new {course = #Model.Course}, null)
CourseController:
public ActionResult CourseEnquiry(string course)
{
// course should have a value at this point
}
In your view, you are only displaying the value of Course.. which isn't able to be submitted. You need to incorporate the value of course with a form input element (textbox, checkbox, textarea, hidden, etc.).
I would highly suggest using EditorFor or Textboxfor, but because your controller action is expecting just a string parameter you could just use Editor or TextBox.
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Html.TextBox(Model.Course, null, new { #class = "form-control"});
<input type="submit" value="Submit" />
}
Then you should just be able to do this in your controller:
public ActionResult CourseEnquiry(string course) // parameter variables are camel-case
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
if(!string.IsNullOrWhiteSpace(course))
model.Course = course;
return model;
}
Let me know if this helps.
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();
}
I have the view that contains the checkbox and Submit button as shown below.
#using (Html.BeginForm())
{
<fieldset>
<legend style="font-size: 100%; font-weight: normal">Delete</legend>
<p> Are you sure you want to delete?</p>
#foreach (string resource in resources)
{
if (resource != "")
{
<input type="checkbox" name="Resources" title="#resource" value="#resource" checked="checked"/>#resource
<br />
}
}
<br />
#Html.HiddenFor(m => m.AttendeeListString)
#Html.HiddenFor(m => m.ResourceListString)
<span class="desc-text">
<input type="submit" value="Yes" id="btnYes" />
</span>
<span class="desc-text">
<input type="submit" value="No" id="btnNo" />
</span>
</fieldset>
}
Below is the Controller code...
public ActionResult DeleteResource(RoomModel roomModel)
{
...
}
RoomModel contains some other data...
Now how can i access the checkbox value in controller?
Note : I have lot more information that need to be send to Controller when i clicked on submit button... Can anybody suggest some solution....
Answer :
I have added these two property to My model
public List<SelectListItem> Resources
{
get;
set;
}
public string[] **SelectedResource**
{
get;
set;
}
And My view check box i have updated as follows
#foreach (var item in Model.Resources)
{
<input type="checkbox" name="**SelectedResource**" title="#item.Text" value="#item.Value" checked="checked"/>#item.Text
<br /><br />
}
And in Controller ...
if (roomModel.SelectedResource != null)
{
foreach (string room in roomModel.**SelectedResource**)
{
resourceList.Add(room);
}
}
Note: The name of check box and Property in the model should be same. In my case it is SelectedResource
You have a few options. The easiest would be:
1) Parameter bind a view model with the Resources property. I recommend this way because it's the preferred MVC paradigm, and you can just add properties for any additional fields you need to capture (and can take advantage of validation easily by just adding attributes).
Define a new view model:
public class MyViewModel
{
public MyViewModel()
{
Resources = new List<string>();
}
public List<string> Resources { get; set; }
// add properties for any additional fields you want to display and capture
}
Create the action in your controller:
public ActionResult Submit(MyViewModel model)
{
if (ModelState.IsValid)
{
// model.Resources will contain selected values
}
return View();
}
2) Parameter bind a list of strings named resources directly in the action:
public ActionResult Submit(List<string> resources)
{
// resources will contain selected values
return View();
}
It's important to note that in the question, the view is creating checkboxes that will send the string value of all checked resources, not boolean values (as you might expect if you used the #Html.CheckBox helper) indicating if each item is checked or not. That's perfectly fine, I'm just pointing out why my answer differs.
In MVC action, have a parameter that corresponds to the name of the checkbox, something like:
bool resources
bool[] resources
use javascript or jquery to collect all the value and post to the controller
var valuesToSend='';
$('input:checked').each(function(){
valuesToSend+=$(this).val() + "$";//assuming you are passing number or replace with your logic.
});
and after submit call ajax function
$.ajax({
url:'yourController/Action',
data:valuesTosend,
dataType:'json',
success:function(data){//dosomething with returndata}
})
or else you can pass the model to controller. if you implemented Model -View-ViewModel pattern.
public class yourViewModel
{
public string Id { get; set; }
public bool Checked { get; set; }
}
Action methods
[HttpPost]
public ActionResult Index(IEnumerable<yourViewModel> items)
{
if(ModelState.IsValid)
{
//do with items. (model is passed to the action, when you submit)
}
}
I'm assuming that the resources variable is generated in the Controller or can be placed onto the ViewModel. If so, then this is how I would approach it:
Your view model would have a Resources dictionary added to it, and would look something like this:
public class RoomModel
{
public Dictionary<string,bool> Resources { get; set; }
// other values...
}
You populate the Resources Dictionary with the names of your resource items as the key (string) and set the "checked" value (bool) to a default state of false.
e.g. (in your [HttpGet] controller)
// assuming that `resource` is your original string list of resources
string [] resource = GetResources();
model.Resources = new Dictionary<string, bool>();
foreach(string resource in resources)
{
model.Resources.Add(resource, false);
}
To render in the view, do this:
#foreach (string key in Model.Resources.Keys)
{
<li>
#Html.CheckBoxFor(r => r.Resources[key])
#Html.LabelFor(r => r.Resources[key], key)
</li>
}
This will then enable the [HttpPost] controller to automatically populate the dictionary onto the Model when you post back:
public ActionResult DeleteResource(RoomModel roomModel)
{
// process checkbox values
foreach(var checkbox in roomModel.Resources)
{
// grab values
string resource = checkbox.Key;
bool isResourceChecked = checkbox.Value;
//process values...
if(isResourceChecked)
{
// delete the resource
}
// do other things...
}
}
I have added these two property to My model
public List<SelectListItem> Resources
{
get;
set;
}
public string[] **SelectedResource**
{
get;
set;
}
And My view check box i have updated as follows
#foreach (var item in Model.Resources)
{
<input type="checkbox" name="**SelectedResource**" title="#item.Text" value="#item.Value" checked="checked"/>#item.Text
<br /><br />
}
And in Controller ...
if (roomModel.SelectedResource != null)
{
foreach (string room in roomModel.**SelectedResource**)
{
resourceList.Add(room);
}
}
Note: The name of check box and Property in the model should be same. In my case it is SelectedResource
I am developing an ASP.NET MVC 3 application in C# and I use Razor. I am now dealing with a problem concerning the binding of objects through ViewModels passed/received to/from the View by the Controller.
Let's make it clear. I have the following ViewModels:
public class ContainerViewModel
{
public int ContainerId {get; set;}
public string ContainerName {get; set;}
public List<ItemPostModel> ItemData {get; set;}
}
public class ItemPostModel
{
public int ItemId {get; set;}
public string ItemName {get; set;}
public int ItemValue {get; set;}
}
The ContainerViewModel is used to pass the data to the View. Its properties ContainerId and ContainerName are used just for display purposes. The List<ItemPostModel> property has to be filled using a Form. The View looks something like this (it is a simplified version):
<strong>#Model.ContainerName</strong>
#using (Html.BeginForm())
{
<fieldset>
#foreach(var item in Model.ItemData)
{
#Html.TextBox(item.ItemId);
#Html.TextBox(item.ItemName);
#Html.TextBox(item.ItemValue);
<p>
<input type="submit" value="Save" />
</p>
}
</fieldset>
}
The Controller corresponding action methods are as follows:
public ActionResult UpdateItems()
{
//fill in the ContainerViewModel lcontainer
return View("UpdateItems", lcontainer);
}
[HttpPost]
public ActionResult UpdateItems(int containerId, ItemPostModel itemData)
{
//store itemData into repository
}
The problem is that with this code the ItemPostModel itemData passed to the Post ActionMethod UpdateItems is always empty. The containerId is correctly passed. Same result if I use the following code in the Controller (obviously not DRY);
[HttpPost]
public ActionResult UpdateItems(ContainerViewModel container)
{
//extract itemData from ContainerViewModel container
//store itemData into repository
}
How can I "teach" the application that I want the form elements stored in the List<ItemPostModel>? Shall I modify the ModelBinder or there is a simpler way to perform this task? Thanks everybody for your answers.
Don't write loops in a view. Use editor templates:
<strong>#Model.ContainerName</strong>
#using (Html.BeginForm())
{
<fieldset>
#Html.EditorFor(x => x.ItemData)
<input type="submit" value="Save" />
</fieldset>
}
and inside the corresponding editor template (~/Views/Shared/EditorTemplates/ItemPostModel.cshtml):
#model ItemPostModel
#Html.TextBox(x => x.ItemId)
#Html.TextBox(x => x.ItemName)
#Html.TextBox(x => x.ItemValue)
And in the controller action you might need to specify the prefix:
[HttpPost]
public ActionResult UpdateItems(
int containerId,
[Bind(Prefix = "ItemData")]ItemPostModel itemData
)
{
//store itemData into repository
}
and that should be pretty much all. The editor template will take care of generating the proper input field names for the binding to work.