Execute code on Post request in ASP.NET MVC - c#

I'm pretty new to ASP.NET MVC and was wondering how to do the following:
I have a list with some buttons like this:
#model List<SitefinityWebApp.Mvc.Models.ProfileModel>
#using (Html.BeginForm())
{
<ul>
#foreach (var item in Model)
{
<li>
<span>#item.EmailAddress</span>
<button id="submit">Generate code</button>
</li>
}
</ul>
}
So when a user clicks on the button, I need to execute some code on the server where I need the email address as a parameter.
I think I would need a POST for this, but I'm not sure how to set this up. In the end I want the same list to be rendered again.
I tried an ActionLink, but this is only for GET requests as I understood?
Any ideas on how to do this?
Thanks,
Daniel

You can define POST function in the Controller. Same name as the GET call.
Your Get Call should like somehing like this:
public ActionResult Index()
{
List<ProfileModel> list = new List<ProfileModel>();
list.add(listItem);
return View(list);
}
Then make the POST function:
[HttpPost]
public ActionResult Index(List<ProfileModel> postModel)
{
var emailAddress = postModel.EmailAddress
// do some stuff here
return RedirectToAction("Index");
}
You can call any POST function with the parameters in the Html.BeginForm:
#using (Html.BeginForm("MothodeName", "ControllerName", FormMethod.Post))
{
<!-- form here -->
}

It's not entirely clear what you want to do. Currently you are rendering multiple buttons in side a single form, but the form has no controls so nothing will post back (and since you have not specified the buttons type attribute, it may not trigger a submit anyway depending on the browser). One way to solve this is to have a form (specifying a route parameter) for each item that posts back the email address and then redirects back to the index page.
#foreach (var item in Model)
{
<span>#item.EmailAddress</span>
#using (Html.BeginForm("ProcessEmail", new { emailAddress = item.EmailAddress }))
{
<button type="submit">Generate code</button>
}
}
and the POST method would be
[HttpPost]
public ActionResult ProcessEmail(string emailAddress)
{
// do something with the email
return RedirectToAction("Index"); // redisplay the page
}
Alternatively you could use a hidden input instead of a route parameter
#foreach (var item in Model)
{
<span>#item.EmailAddress</span>
#using (Html.BeginForm("ProcessEmail"))
{
#Html.HiddenFor(m => item.EmailAddress , new { id = "" }) // remove the id attribute to prevent invalid html
<button type="submit">Generate code</button>
}
}
However, to get far better performance and avoid having to regenerate the view each time, you can use ajax to post the value
#foreach (var item in Model)
{
<span>#item.EmailAddress</span>
<button type="button" class="process-email" data-email="#item.EmailAddress">Generate code</button>
}
var url = '#Url.Action("ProcessEmail")';
$('.process-email').click(function() {
$.post(url, { emailAddress: $(this).data('email') }, function(response) {
if(response) {
// processing succeeded - display message?
} else {
// processing failed = display error?
}
})
})
and modify the method to
[HttpPost]
public JsonResult ProcessEmail(string emailAddress)
{
// do something with the email
return Json(true); // or return Json(null) if an error occured
}

Related

Mvc View rendering

I'm trying to create contact us page where user fill's in the detail and submit and at the bottom display message which comes from server.
The way i have implemented is something like this.
[HttpGet]
public ActionResult ContactUs()
{
//Process the stuff
return View("~Views/Contact/Contact.cshtml", model)
}
now when page load it hits above method and display form with the layout including header and footer.
Once user submits form it hits below method
[HttpPost]
public ActionResult ContactUs(ContactUs form)
{
//Process the stuff
View.Message="Thank you for your enquiry."
return View("~Views/Contact/Contact.cshtml", model)
}
It returns to the same page but it doesnt render the body layout not even header or footer simply display outputs form.
Not sure what im doing wrong there, is there any better approach ?
Thanks
Based on the code above, I believe you're attempting something like:
public class UxController : Controller
{
public ActionResult WithResponse(ActionResult result, string message)
{
PageResponse(message);
return result;
}
protected void PageResponse(string message)
{
TempData["Ux_Response"] = message;
}
}
That would be your Controller, then the Controller for that specific page, it would look like:
public class HomeController : UxController
{
public ActionResult Index()
{
return View();
}
public ActionResult SubmitForm(string message)
{
return WithResponse(RedirectToAction("Index"), "Thank you for feedback.");
}
}
Then in your front-end code, you would do the following:
#if(TempData["Ux_Response"] != null)
{
<div>#TempData["Ux_Response"]</div>
}
<form action="/Home/SubmitForm" method="post">
<input type="text" name="message" />
<input type="submit" value="Submit" />
</form>
Obviously you could enhance this, with more versatility. However, you're relying on Post, which will cause a screen flicker. So the better route, may be to do Ajax, then return a JsonResult. Hopefully this helps you out.
It should work if you change your controller/view like this.
Controller;
public ActionResult Contact(ContactModel model)
{
ViewBag.Message = "Your contact page.";
return View(model);
}
public ActionResult SaveContact(ContactModel model)
{
//process values in your model and then rest model
ContactModel.Message = "Thank you for contacting us"; //show thank you message
return RedirectToAction("Contact",model);
}
View;
#model MvcApplication1.Models.ContactModel
#{
ViewBag.Title = "Contact";
}
#using (Html.BeginForm("SaveContact", "Home", Model, FormMethod.Post))
{
#Html.DisplayFor(m => m.Message);
<button type="submit">Submit</button>
}
I manged to solve this. the issue was the because i was using sitecore cms the form action wasnt processing it full work flow, after i removed the action, it defaults to action method which defined in cms and triggers the cms workflow.

IEnumerable<SelectListItem> error in Simple Membership in ASP.NET MVC 5

I use Simple Membership in ASP.NET MVC 5 to use my existing SQL Server. In Register page I use dropdownbox to polpulate the user roles. In my Account controller I use [HttpGet] and [HttpPost] for Register Action. When I run my web app and goto Register page I can see my DropDownBox poplulated with data from my SqlServer.
But when I try to create new user and select a role I get error as;
"There is no ViewData item of type 'IEnumerable' that has the key 'roleName'"
Here is my Account Controller code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SimpleMemberShip.Models;
using WebMatrix.WebData;
using System.Web.Security;
namespace SimpleMemberShip.Controllers
{
public class AccountController : Controller
{
// ****** REGISTER PAGE ACTION ******
[HttpGet]
public ActionResult Register()
{
//Roles for DropDown Box
IEnumerable<SelectListItem> RolesList = new System.Web.Mvc.SelectList(System.Web.Security.Roles.GetAllRoles(), "roleName");
ViewData["roleName"] = RolesList;
return View();
}
// ****** REGISTER PAGE ACTION with PARAMETERS ******
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Register(Register registerdata, string RoleName)
{
if (ModelState.IsValid)
{
try
{
WebSecurity.CreateUserAndAccount(registerdata.Username, registerdata.Password);
System.Web.Security.Roles.AddUserToRole(registerdata.Username, RoleName);
return RedirectToAction("Index", "Home");
}
catch (MembershipCreateUserException exception)
{
ModelState.AddModelError("", "Warning: Username exist...");
return View(registerdata);
}
}
ModelState.AddModelError("", "Warning: Username exist....");
return View(registerdata);
}
}
}
And here is the my Register.cshtml code look like:
#using SimpleMemberShip.Models
#model Register
#{
ViewBag.Title = "Register";
}
<h2>New User Registration</h2>
<br />
<div>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<label>User Name:</label>
#Html.TextBoxFor(m => m.Username, new { #class = "form-control" })
<br />
<label>User Password:</label>
#Html.PasswordFor(m => m.Password, new { #class = "form-control" })
<br />
<label>Roles</label>
#Html.DropDownList("roleName", (System.Web.Mvc.SelectList)ViewBag.roleName, "Select a Role")
<br />
<br />
<button class="btn btn-primary">Register</button>
}
</div>
Does anyone know where I am making mistakes?
Kind Regards,
What most likely happens in that you have some kind of error in the way you register a user, and the validation fails. In that case you are returning the same view, which is fine:
return View(registerdata);
However you are not repopulating the ViewData["roleName"] element which the view depends on. Since ASP.NET MVC does not maintain any kind of state, this element needs to be repopulated on each request. So consider doing that, exactly the same way you were doing it before in the GET action, and the error should go away.
As a side note, you are mixing two ways of passing data from controller into view, model and ViewData. This is not very maintainable and obvious, so I suggest you stick to model approach only. Which should not be very hard - add a list of role names as a property of the model, and make sure to initialize it on every request.
In your HttpPost action, If some error happens, or if your model validation fails, you are returning the same model to the same view. So you need to reload the ViewData["roleName"] with the Role list collection again as your view is trying to read from it to load the dropdown.
Remember, HTTP is stateless. ASP.NET MVC does not work like web forms where it stores the form data in view state across multiple http requests(form posts)
My recommendation is to not use dynamic stuff like ViewBag and ViewData. You should be using a strongly typed viewmodel to transfer data from your action methopd to your views.
so create a view model like
public class CreateUser
{
public string UserName {set;get;}
public string Password{set;get;}
public List<SelectListItem> RolesList {set;get;}
public int SelectedRoleId {set;get;}
}
And in your GET action method, You get all the roles ( Which could be a collection of Role class or some custom class), convert that to a collection of SelectListItem and assign that to the RolesList property of the CreateUser object
public ActionResult Register()
{
var vm=new CreateUser();
vm.RolesList =GetRoles();
return View(vm);
}
private List<SelectListItem> GetRoles()
{
var allRoles = System.Web.Security.Roles.GetAllRoles();
return allRoles.Select(s => new SelectListItem
{
Value = s.roleId.ToString(),
Text = s.roleName
});
}
and in your razor view which is bound to CreateUser class.
#using YourNamespace.CreateUser
#using(Html.Beginform())
{
#Html.TextBoxFor(s=>s.UserName)
#Html.TextBoxFor(s=>s.Password)
#Html.DropDownListFor(x=> x.SelectedRoleId, Model.RolesList,"Select")
<input type="submit" />
}
And now in your Http post action method, Make sure you reload the RolesList collection before you return the posted model back to the same view (If Mode Validation fails or an Exception occurs)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Register(CreateUser model)
{
if (ModelState.IsValid)
{
try
{
// to do : Save something
// read model.UserName & model.SelectedRoleId etc.
return RedirectToAction("Index", "Home");
}
catch (MembershipCreateUserException exception)
{
ModelState.AddModelError("", "Warning: Username exist...");
//Reload the data now
model.RolesList =GetRoles();
return View(model);
}
}
ModelState.AddModelError("", "Warning: Username exist....");
//Reload the data now
model.RolesList =GetRoles();
return View(model);
}

MVC 4 ViewModel not being sent back to Controller

I can't seem to figure out how to send back the entire ViewModel to the controller to the 'Validate and Save' function.
Here is my controller:
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel transaction)
{
}
Here is the form in the view:
<li class="check">
<h3>Transaction Id</h3>
<p>#Html.DisplayFor(m => m.Transaction.TransactionId)</p>
</li>
<li class="money">
<h3>Deposited Amount</h3>
<p>#Model.Transaction.Amount.ToString() BTC</p>
</li>
<li class="time">
<h3>Time</h3>
<p>#Model.Transaction.Time.ToString()</p>
</li>
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
#Html.TextBoxFor(m => m.WalletAddress, new { placeholder = "Wallet Address", maxlength = "34" })
<input type="submit" value="Send" />
#Html.ValidationMessage("walletAddress", new { #class = "validation" })
}
When i click on submit, the conroller contains the correct value of the walletAddress field but transaction.Transaction.Time, transaction.Transaction.Location, transaction.Transaction.TransactionId are empty.
Is there a way i could pass the entire Model back to the controller?
Edit:
When i dont even receive the walletAddress in the controller. Everything gets nulled!
When i remove this line alone: #Html.HiddenFor(m => m.Transaction.TransactionId);
it works and i get the Token property on the controller, but when i add it back, all the properties of the transaction object on the controller are NULL.
Here is the BitcoinTransactionViewModel:
public class BitcoinTransactionViewModel
{
public string Token { get; set; }
public string WalletAddress { get; set; }
public BitcoinTransaction Transaction { get; set; }
}
public class BitcoinTransaction
{
public int Id { get; set; }
public BitcoinTransactionStatusTypes Status { get; set; }
public int TransactionId { get; set; }
public decimal Amount { get; set; }
public DateTime Time { get; set; }
public string Location { get; set; }
}
Any ideas?
EDIT: I figured it out, its in the marked answer below...
OK, I've been working on something else and bumpend into the same issue all over again.
Only this time I figured out how to make it work!
Here's the answer for anyone who might be interested:
Apparently, there is a naming convention. Pay attention:
This doesn't work:
// Controller
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel transaction)
{
}
// View
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
.
.
This works:
// Controller
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel **RedeemTransaction**)
{
}
// View
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { **RedeemTransaction** = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
.
.
In other words - a naming convention error! There was a naming ambiguity between the Model.Transaction property and my transaction form field + controller parameter. Unvelievable.
If you're experiencing the same problems make sure that your controller parameter name is unique - try renaming it to MyTestParameter or something like this...
In addition, if you want to send form values to the controller, you'll need to include them as hidden fields, and you're good to go.
The signature of the Send method that the form is posting to has a parameter named transaction, which seems to be confusing the model binder. Change the name of the parameter to be something not matching the name of a property on your model:
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel model)
{
}
Also, remove the htmlAttributes parameter from your BeginForm call, since that's not doing anything useful. It becomes:
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post))
Any data coming back from the client could have been tampered with, so you should only post back the unique ID of the transaction and then retrieve any additional information about it from your data source to perform further processing. You'll also want to verify here that the user posting the data has access to the specified transaction ID since that could've been tampered with as well.
This isn't MVC specific. The HTML form will only post values contained within form elements inside the form. Your example is neither inside the form or in a form element (such as hidden inputs). You have to do this since MVC doesn't rely on View State. Put hidden fields inside the form:
#Html.HiddenFor(x => x.Transaction.Time)
// etc...
Ask yourself though.. if the user isn't updating these values.. does your action method require them?
Model binding hydrates your view model in your controller action via posted form values. I don't see any form controls for your aforementioned variables, so nothing would get posted back. Can you see if you have any joy with this?
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.TextBoxFor(m => m.WalletAddress, new { placeholder = "Wallet Address", maxlength = "34" })
#Html.Hidden("Time", Model.Transaction.Time)
#Html.Hidden("Location", Model.Transaction.Location)
#Html.Hidden("TransactionId", Model.Transaction.TransactionId)
<input type="submit" value="Send" />
#Html.ValidationMessage("walletAddress", new { #class = "validation" })
}
Try to loop with the folowing statement not with FOREACH
<table>
#for (var i = 0; i < Model.itemlist.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(x => x.itemlist[i].Id)
#Html.HiddenFor(x => x.itemlist[i].Name)
#Html.DisplayFor(x => x.itemlist[i].Name)
</td>
</tr>
}
</table>
Try Form Collections and get the value as. I think this may work.
public ActionResult Send(FormCollection frm)
{
var time = frm['Transaction.Time'];
}
Put all fields inside the form
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post))
and make sure that the model
BitcoinTransactionViewModel
included in view or not?
Can you just combine those 2 models you have? Here's how I do it with one model per view...
1. I use Display Templates from view to view so I can pass the whole model as well as leave data encrypted..
2. Setup your main view like this...
#model IEnumerable<LecExamRes.Models.SelectionModel.GroupModel>
<div id="container">
<div class="selectLabel">Select a Location:</div><br />
#foreach (var item in Model)
{
#Html.DisplayFor(model=>item)
}
</div>
3. Create a DisplayTemplates folder in shared. Create a view, naming it like your model your want to pass because a DisplayFor looks for the display template named after the model your using, I call mine GroupModel. Think of a display template as an object instance of your enumeration. Groupmodel Looks like this, I'm simply assigning a group to a button.
#model LecExamRes.Models.SelectionModel.GroupModel
#using LecExamRes.Helpers
#using (Html.BeginForm("Index", "Home", null, FormMethod.Post))
{
<div class="mlink">
#Html.AntiForgeryToken()
#Html.EncryptedHiddenFor(model => model.GroupKey)
#Html.EncryptedHiddenFor(model => model.GroupName)
<p>
<input type="submit" name="gbtn" class="groovybutton" value=" #Model.GroupKey ">
</p>
</div>
}
4. Here's the Controller.
*GET & POST *
public ActionResult Index()
{
// Create a new Patron object upon user's first visit to the page.
_patron = new Patron((WindowsIdentity)User.Identity);
Session["patron"] = _patron;
var lstGroups = new List<SelectionModel.GroupModel>();
var rMgr = new DataStoreManager.ResourceManager();
// GetResourceGroups will return an empty list if no resource groups where found.
var resGroups = rMgr.GetResourceGroups();
// Add the available resource groups to list.
foreach (var resource in resGroups)
{
var group = new SelectionModel.GroupModel();
rMgr.GetResourcesByGroup(resource.Key);
group.GroupName = resource.Value;
group.GroupKey = resource.Key;
lstGroups.Add(group);
}
return View(lstGroups);
}
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Index(SelectionModel.GroupModel item)
{
if (!ModelState.IsValid)
return View();
if (item.GroupKey != null && item.GroupName != null)
{
var rModel = new SelectionModel.ReserveModel
{
LocationKey = item.GroupKey,
Location = item.GroupName
};
Session["rModel"] = rModel;
}
//So now my date model will have Group info in session ready to use
return RedirectToAction("Date", "Home");
}
5. Now if I've got alot of Views with different models, I typically use a model related to the view and then a session obj that grabs data from each model so in the end I've got data to submit.
The action name to which the data will be posted should be same as the name of the action from which the data is being posted. The only difference should be that the second action where the data is bein posted should have [HttpPost] and the Posting method should serve only Get requests.

MVC4 - NullReferenceException being thrown in view when trying to use a list

On my home (index) page I have two partials, one renders a search form and the other results from the search:
<div class="row-fluid well">
<div class="span6">
#Html.Partial("~/Views/Search/_BasicPropertySearchPartial.cshtml")
</div>
<div class="span6" id="basic-property-search-results">
#Html.Partial("~/Views/Search/_BasicPropertySearchResultsPartial.cshtml")
</div>
</div>
In my SearchController a GET action returns the search form:
[HttpGet]
public ActionResult BasicPropertySearch()
{
return PartialView("_BasicPropertySearchPartial");
}
And a POST action gets user input from the form and returns results based on a query:
[HttpPost]
public ActionResult BasicPropertySearch(BasicPropertySearchViewModel viewModel)
{
var predicate = PredicateBuilder.True<ResidentialProperty>();
if (ModelState.IsValid)
{
using(var db = new LetLordContext())
{
predicate = predicate.And(x => x.HasBackGarden);
//...
var results = db.ResidentialProperty.AsExpandable().Where(predicate).ToList();
GenericSearchResultsViewModel<ResidentialProperty> gsvm =
new GenericSearchResultsViewModel<ResidentialProperty> { SearchResults = results };
return PartialView("_BasicPropertySearchResultsPartial", gsvm);
}
}
ModelState.AddModelError("", "Something went wrong...");
return View("_BasicPropertySearchPartial");
}
I've created a generic view model because search results may be lists of different types:
public class GenericSearchResultsViewModel<T>
{
public List<T> SearchResults { get; set; }
public GenericSearchResultsViewModel()
{
this.SearchResults = new List<T>();
}
}
The POST action returns the following view:
#model LetLord.ViewModels.GenericSearchResultsViewModel<LetLord.Models.ResidentialProperty>
#if (Model.SearchResults == null) // NullReferenceException here!
{
<p>No results in list...</p>
}
else
{
foreach (var result in Model.SearchResults)
{
<div>
#result.Address.Line1
</div>
}
}
I've put breakpoints on the GET and POST actions and the exception is being thrown before either or hit.
Is this problem being caused because index.cshtml is being rendered before it has a chance to do the GET/POST in the SearchController?
If so, does this mean it's a routing problem?
Finally, I thought newing SearchResults in the constructor would overcome NullReferenceExceptions?
Feedback appreciated.
It seems that the whole Model is null. You need to either supply a partial view model to the Html.Partial calls or use Html.Action to call the two controller (child) actions with the action and controller names.
See the MSDN for details.

Performing search in Asp.net MVC

I am new to Asp.net MVC and have no idea as to how can i perform the search. Here's my requirement, please tell me how will you handle this :-
I need to have textbox where user can enter a search query or string. The user then clicks on a button or presses enter to submit it. The string needs to matched with a table's property name.
NOTE:- Querying the data and fetching the result isn't the main point here. All I need to know is how will you take the user input and pass it to a controller action or whatever for further processing. Just tell me how will you read the user input and where will you send it to search.
Asp.Net MVC uses standard HTTP verbs. For the html part, it's a normal html form that points to an url. Server side, that url will be routed to a controller/action which will handle the input and do what is needed.
Let's have a sample. You want to make a search form. First of all, it's a best practice to have search forms use the HTTP GET method instead of POST, so the search results can be bookmarked, linked, indexed, etc. I won't be using Html.BeginForm helper method to make things more clear.
<form method="get" action="#Url.Action("MyAction", "MyController")">
<label for="search">Search</label>
<input type="text" name="search" id="search" />
<button type="submit">Perform search</button>
</form>
That's all the html you need. Now you'll have a controller called "MyController" and the method will be something like this:
[HttpGet]
public ActionResult MyAction(string search)
{
//do whatever you need with the parameter,
//like using it as parameter in Linq to Entities or Linq to Sql, etc.
//Suppose your search result will be put in variable "result".
ViewData.Model = result;
return View();
}
Now the view called "MyAction" will be rendered, and the Model of that view will be your "result". Then you'll display it as you wish.
As always in an ASP.NET MVC application you start by defining a view model which will express the structure and requirements of your view. So far you have talked about a form containing a search input:
public class SearchViewModel
{
[DisplayName("search query *")]
[Required]
public string Query { get; set; }
}
then you write a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new SearchViewModel());
}
[HttpPost]
public ActionResult Index(SearchViewModel model)
{
if (!ModelState.IsValid)
{
// There was a validation error => redisplay the view so
// that the user can fix it
return View(model);
}
// At this stage we know that the model is valid. The model.Query
// property will contain whatever value the user entered in the input
// So here you could search your datastore and return the results
// You haven't explained under what form you need the results so
// depending on that you could add other property to the view model
// which will store those results and populate it here
return View(model);
}
}
and finally a view:
#model SearchViewModel
#using (Html.BeginForm())
{
#Html.LabelFor(x => x.Query)
#Html.EditorFor(x => x.Query)
#Html.ValidationMessageFor(x => x.Query)
<button type="submit">Search</button>
}
This is the best way to do it.
Create a ViewModel
public class SearchViewModel
{
public string Query { get; set; }
}
Create a Controller
public class SearchController : Controller
{
[HttpPost]
public ActionResult Search(SearchViewModel model)
{
// perform search based on model.Query
// return a View with your Data.
}
}
Create the View
// in your view
#using (Html.BeginForm("Search", "SearchController"))
{
#Html.TextBox("Query")
<input type="submit" value="search" />
}
hope this helps

Categories