System.ArgumentNullException occured in System.Web.Mvc.dll - c#

I have an application where I need to input data and on submit the data gets saved in database. When I checked in database the input is getting saved successfully but I am getting an exception when the page reloads after httppost.
I am getting exception at :
#Html.DropDownList("LineID", new SelectList(Model.dropConfig, "LineID", "LineID"), "-- Select LineID --", new { required = true, #class = "form-control" })
controller code to get the dropdownlist values, binding with Db:
[ActionName("DetailsForm")]
[HttpGet]
public ActionResult DetailsForm()
{
try
{
var model = new DetailsViewModel() { dropConfig = floorService.DropDownList().ToList() };
return View("DetailsForm", model);
}
catch (Exception ex)
{
return View("_error");
}
}
controller code to http post:
[ActionName("DetailsForm")]
[HttpPost]
public ActionResult DetailsForm(DetailsViewModel model, FormCollection form)
{
DetailsConfiguration detailsConfig = new DetailsConfiguration();
detailsConfig.LineID = Convert.ToString(form["LineID"]);
//Similary for other fields
floorService.SaveDetails(detailsConfig);
ModelState.Clear();
ViewBag.message = "Success";
return View("DetailsForm",model);
}
Snapshot of exception:

Because your view code is using Model.dropConfig to build the SelectList for your dropdown, and you are not setting the dropConfig property value before returning to the view.
Remember, Http is stateless. So even though you set the dropConfig property value in the GET action, It won't be available inside your HttpPost action. When you submit your form, it is a totally new request to the server.
You can fix it by loading dropConfig property again.
model.dropConfig = floorService.DropDownList().ToList();
return View(model);
But ideally you should be following the P-R-G pattern.
P-R-G stands for Post-Redirect-Get. So when you submit your form to an http post action method, you should return a redirect response and the browser will make a new GET call to that action method.
You can use the RedirectToAction method to return a redirect response.
floorService.SaveDetails(detailsConfig);
return RedirectToAction("DetailsForm");
This will send a 302 response back to the browser with the location header set to the url to the DetailsForm action method and the browser will make a new GET request to that.
ViewBag won't work when with redirect response. So you might consider using TempData. TempData can be used to transfer between two requests.
TempData["message"] = "Success";
return RedirectToAction("DetailsForm");
Now you can read TempData["message"] in the DetailsForm action method or the view rendered by that.
For example, you can read it in the view (rendered by DetailsForm GET action method) like this
#if (TempData["message"]!=null)
{
<div class="alert alert-success" id="alert">
<button type="button" class="close" data-dismiss="alert">x</button>
<strong>Success! </strong>#TempData["message"]
</div>
}

Related

GET method executing before POST method after a page submit?

After editing a form and clicking a Save button, the HttpGet method is being executed before the HttpPost method. The page is reloading with the query string in the URL, and the old data still populating the fields, but the data has been saved on the server side. If I remove the query string and reload the page, the new data appears.
My expectation is that only the HttpPost method would be called, changes would be saved saved, then the page would be loaded back up with the saved changes.
Using the Microsoft.AspNetCore.Mvc": "1.0.0 package.
Here are my HttpGet and HttpPost methods:
[HttpGet]
[Route("~/Home/Activity/{activityId}")]
public IActionResult Activity(int activityId)
{
ViewData["Title"] = "Activity Detail";
FundraiserDBContext context = new FundraiserDBContext(_ServerName, EnvironmentCode);
Engagement activity;
if (activityId == -1)
{
activity = new Engagement();
context.Engagement.Add(activity);
}
else
{
activity = context.Engagement.FirstOrDefault(a => a.Id == activityId);
}
if (activity != null)
{
ActivityViewModel vmActivity = new ActivityViewModel(activity, context);
return View("Activity", vmActivity);
}
else
{
ActivityViewModel vmActivity = new ActivityViewModel(context);
return View("Activity", vmActivity);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
//[Route("~/Home/Activity/{activityId}")]
public IActionResult Activity(ActivityViewModel vmActivity)
{
FundraiserDBContext db = new FundraiserDBContext(_ServerName, EnvironmentCode);
if (ModelState.IsValid)
{
db.Engagement.Update(vmActivity.ToEngagement(db));
db.SaveChanges();
}
return View("Activity", vmActivity); //this was vm.EngagementId
}
And here is the code for the Save button:
<button type="submit" class="btn-success pull-right" style="width:80px;" onclick="location.href='#Url.Action("Activity", "Home", #Model)'">Save</button>
Remove redirect from post method, because before returning the View its redirecting to the Index method without updated model
Redirect($"~/Home/Index"); // remove this line
Matjaž Mav found my error and described it in the comment below the original post. I mistakenly thought I needed the onclick event on my button. Removing this resulted in the expected behavior I was looking for.
The button code now looks like this:
<button type="submit" class="btn-success pull-right" style="width:80px;">Save</button>

Search page doesn't show up following HttpGet request MVC c#

I have created a page that takes in a string that will search a list of vendors. My goal is to output them to a grid list on an HTML page. Oddly enough, the first page loads, and I can break point the code until the return view of the actual list page. However, it never actually loads. It is even more frustrating because if I don't pass the model to the grid page, it gives me the typical "You can't use a null model", but then it still doesn't load the new page. I have tried several versions. The most current is below.
[HttpPost]
public ActionResult Search(String searchString)
{
this.searchString = searchString;
List<VendorInvoice> v = VendorSearches.publicSearch(searchString);
test = v;
ViewData.Model = v;
TempData.Add("test",v);
return RedirectToAction("Search");
}
[HttpGet]
public ActionResult Search()
{
List<VendorInvoice> v = (List<VendorInvoice>)TempData["test"];
return View("Search",v);
}
So if I take the v out, then I get the error about not passing the model. If it is there, then nothing will happen. The new page won't load.
In your HttpPost search action method, you are setting the result data to show in TempData and doing calling the RedirectToAction method.
RedirectToAction returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action. That means, it will be totally new request coming to your search GET action again. Since Http is stateles, it does not have any idea about what you did in your previous request. The data stored in TempData won't be available to this request.
What you should be doing is, similar to your GET action method, simply
return the result to the view.
[HttpPost]
public ActionResult Search(String searchString)
{
this.searchString = searchString;
List<VendorInvoice> v = VendorSearches.publicSearch(searchString);
return View("Search",v);
}
That should fix your problem. But as Stephen Muecke mentioned, you can keep just your GET action method for your Initial view and search result view
public ActionResult Search(String searchString="")
{
List<VendorInvoice> v = new List<VendorInvoice>();
v = VendorSearches.publicSearch(searchString);
return View("Search",v);
}
And your view
#model List<VendorInvoice>
#using(Html.BeginForm("Search","YourControllerName",FormMethod.GET)
{
<input type="text" name="searchString" />
<input type="submit" />
}
<h2>Results</h2>
#foreach(var item in Model)
{
<p> #item.SomePropertyNameOfYourVendorInvoiceHere </p>
}

MVC #Html.ActionLink no-op from controller

I have an #Html.ActionLink inside of a partial view that when clicked I'd like to have either send the user to another view or stay on the current view without changing anything. Is this possible?
Our controller looks like:
public ActionResult Edit(int id)
{
if (ShouldAllowEdit(id))
{
return this.View("Edit", ...edit stuff...)
}
return ????????
}
We tried return new EmptyResult(); but that just dumps the user to a blank page.
This is a little different approach to the issue, but it should do what you want.
Instead of giving the user a link to navigate to, do an ajax call on link/button click, and do the id check. Return either the url to navigate to in a JsonResult, or nothing if the id is invalid.
On return of the ajax call, navigate to the url if appropriate.
(swap out the hard coded ids and the == 0 with your ShouldAllowEdit function in the example of course)
In the View:
<div class="btn btn-danger" id="myButton">Button</div>
#section scripts{
<script>
$("#myButton").click(function () {
$.ajax("#Url.Action("Edit", new { id = 0 })", { type : "POST" })
.success(function (data) {
if (data.url !== "") {
window.location.href = data.url;
}
});
});
</script>
}
In the controller:
[HttpPost]
public JsonResult Edit(int id)
{
if (id == 0)
{
return Json(new {url = ""});
}
else
{
return Json(new { url = Url.Action("EditPage", new { id = id }) });
}
}
An answer is to redirect to the view action - and maybe give some feed back why they failed.
public ActionResult Edit(int id)
{
if (ShouldAllowEdit(id))
{
return this.View("Edit", ...edit stuff...)
}
ModelState.AddModelError("id", "Not allowed to edit this item");
return RedirectToAction(Edit(id));
}
If the user clicks a link they will be taken away. They might be sent back right to the same page, but the page will unload, be requested from the server again, and then re-rendered in the browser. If you don't want that to happen, you don't give the user the link in the first place. In other words, conditionally render the link or not based on the user's roles or whatever.
#if (userCanEdit)
{
#Html.ActionLink(...)
}
Where userCanEdit is whatever logic you need to make that determination.
If the user fails whatever check you determine, then they don't get the link. Simple.
However, since there's malicious people in the world, you can't just leave it entirely there. There's potential for the user to figure out the link to edit something and go there manually. So, to prevent that you check for the edit permission in your action (like you've already got in your code sample), but if the user is not allowed, then you just return a forbidden status code:
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
Or
return new HttpStatusCodeResult(403);
They both do the same thing.
UPDATE
Based on your comment above, it appears that the user is normally allowed to edit but can't in a particular instance because another user is editing. A 403 Forbidden is not appropriate in that case, so really all you've got is a simple redirect back to the page they were on, perhaps with a message explaining why they're back there.
TempData["EditErrorMessage"] = "Sorry another user is editing that right now.";
return RedirectToAction("Index");

MVC Just populate viewbag once, between page default action result and httppost

Let's say I populate a viewbag object in my page action result like this
public ActionResult MiEmployeeDetails()
{
var employees = _db.geo_employees.Select(x => new SelectListItem
{
Value = x.name,
Text = x.name
}).ToList();
ViewBag.Employees = employees;
return View();
}
and then populate in my view as such
<%= Html.DropDownList("Employees") %>
That's fine but then when I hit the submit button and it goes to Httppost Action Result i.e.
[HttpPost]
public ActionResult MiEmployeeDetails(FormCollection fc)
{
return View();
}
I get a null reference exception on my viewbag option. So in my Httppost action result do I also need to reset the viewbag object created in my original page load action result ?
Each request is completely independent. You need to reinitialize all view data if you call the view again from a post. The only exemption is data that is sent from the Client during the post

ValidationSummary not appearing with Partial Views

I have this problem:
I go to a page such as:
/Auction/Details/37
and this calls this action method:
public ActionResult Details(int id)
A particular line in this method is:
return View("DetailsLub", auction);
This view contains this line:
#Html.Action("BidOnAuction", new { auctionId = Model.Id })
Which calls this action method:
public PartialViewResult BidOnAuction(int auctionId)
So far so good?
Now, I have a form in the BidOnAuction view, whcih has a button. When I click on this button, this action method is invloked:
[HttpPost]
public ActionResult BidOnAuction(BidOnAuctionViewModel model)
This action method has a catch statement with the following lines:
ModelState.AddModelError(string.Empty, operation + #" Failure: " + message);
return RedirectToAction("Details", new { id = model.AuctionId });
Now, both the DetailsLUB view and the BidOnAction view contain this line:
#Html.ValidationSummary(true)
But, the issue is that nothing ever gets printed to the screen. What am I doing wrong?
InOrder to get the validation Message on the page you need to return view with Model, as model has the Model State within it, something like this:
return View(Model);
This will return the model BidOnAuction with Validation Summary.
This line of code
return RedirectToAction("Details", new { id = model.AuctionId });
Returns instance of RedirectResult class. That is generally used for redirections and does not render view. If you want to render child action into parent view using #Html.Action, you need to return view from that child action, not RedirectResult. And that RedirectResult will not work even when there's no child action. Returning RedirectResult causes browser to issue fresh, all new request to that action. And model state is lost anyways. You should do something like
try
{
//some actions
return RedirectResult("Details", new { id = model.AuctionId });
}
catch
{
ModelState.AddModelError(string.Empty, operation + #" Failure: " + message);
return View("Details", new { id = model.AuctionId });
}
You can't redirect to a new action and expect the modelstate to be there.
If the modelState is invalid just return (with View(model))
else
redirect to details.
If you need the error information in the details view you will have add it to TempData or pass it in as an optional parameter.

Categories

Resources