return view, but also jump to div - c#

I've got a page with some server side validation. It works, in that on a server validation failure it displays the same page, with a box with the validation errors above the form where the data was input.
My problem is that there is a whole bunch of other stuff at the top of the page, so the user isn't directed to the error box, in fact it could be off screen. The errors are in a div #server_errors. What I want is to tell the controller to return the view, but jump to the errors section, the same as appending #server_errors to the url.
the controller returns like this:
public ActionResult ChangeRiskCategory(Guid id)
{
//...
//call server side method, handle errors
//...
return View("ChangeRiskCategory", changeRiskCategoryModel);
}
and I can't see a way to inject the div id into the view at this point. I can see validating client side would solve this problem but it needs to work without js enabled so I think that rules that out.

You could try something like this:
public ActionResult ChangeRiskCategory(Guid id, bool error = false)
{
//...
//call server side method, handle errors
//...
if (!error && !ModelState.IsValid /*or other way of working out the error will be displayed*/)
{
return Redirect(Url.Action("ChangeRiskCategory") + "?id=" + id + "&error=true#server_error");
}
return View("ChangeRiskCategory", changeRiskCategoryModel);
}
(The "error" parameter is to stop it endlessly redirecting)
If you change your mind about using javascript you could simply emit:
location.href='#server_errors';

What about passing the div id as a property of the ViewModel to the View? Then you can do whatever you want with it in Javascript in the view. After all, the ViewModel represents the data and state of the View.

Use model based validation and have you view look something like this
#model SampleApplication.Models.BasicDemoModel
<form id="AjaxForm" action="/">
<table>
<tr>
<td>#Html.LabelFor(x => x.Name)</td>
<td>
#Html.TextBoxFor(x => x.Name)
#Html.ValidationMessageFor(x => x.Name, "*")
</td>
</tr>
<tr>
<td>#Html.LabelFor(x => x.Email)</td>
<td>
#Html.TextBoxFor(x => x.Email)
#Html.ValidationMessageFor(x => x.Email, "*")
</td>
</tr>
#{
Html.RenderPartial("Address", Model);
}
</table>
#if (!string.IsNullOrWhiteSpace(Model.Message))
{
<h2>#Model.Message</h2>
}
#if (!ViewContext.ViewData.ModelState.IsValid)
{
#Html.ValidationSummary()
}
<input type="submit" title="Submit Form" onclick="PostFormWithAjax();return false;" />
</form>
if you need extra validation messages you can just add it to the more state like this
public ActionResult ChangeRiskCategory(Guid id)
{
//...
//call server side method, handle errors
//...
ModelState.AddModelError("MyInput","This isn't Right");
return View("ChangeRiskCategory", changeRiskCategoryModel);
}

Related

ASP.NET MVC Post method : Model properties does not persit the value

I have 2 ASP.NET MVC action methods, I call the first method by passing and load some initial data, then I get some additional details from UI and call the second action method (Post action method from .cshtml). The data I received from the first call is missing in the post method. can anyone help me what am I doing wrong or missing here?
Action methods:
[Route("setprofile")]
public ActionResult SetProfile(string id)
{
ProfileData data = new ProfileData();
//do something
data.name= getData(id);
return this.View(data);
}
[Route("setprofile")]
[HttpPost]
public ActionResult SetProfile(ProfileData data)
{
// Here I'm not missing the data.name field value
}
View .cshtml file:
<div class="panel-body">
#using (Html.BeginForm("SetProfile", "Home", FormMethod.Post))
{
<div>
<h3> Name: #(this.Model.name)</h3>
</div>
<h3>
Comments:#Html.TextBoxFor(m => m.comments)
</h3>
}
I get the comments value but not getting the name field value from the model here.
Note: I need to display the value I received from the first action method as a label, not text box.
There are two things, Name is writen as text and in order to send back to server, you need to put it inside input element.
IF you dont want to show it #Html.HiddenFor(m => m.name) creates hidden input element.
Other than this, check ModelState for validation errors..
if (!ModelState.IsValid)
return BadRequest(ModelState);
.... your code here
if your model is not valid, the ProfileData returns result
You haven't added an input element for it to be sent back to the server when the form is submitted. If you don't want it to be visible, whilst still being posted back, add a hidden field for it:
#Html.HiddenFor(m => m.name)
Without that, all you're doing is rendering name to the markup but, once the form is submitted, it won't be sent back. Alternatively, you could render a textbox for the value whilst setting its readonly attribute. That would allow it to be visible, not changed, and still be sent back to the server.
#Html.TextBoxFor(m => m.name, new { #readonly = "readonly" })

Hide elements in the view if the model state is invalid

Imagine a simple form that takes an email input like this:
#using (Html.BeginForm("save", "email", FormMethod.Post, new { #id = "my__form" }))
{
<div class="field">
#Html.LabelFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
#Html.TextBoxFor(model => model.Email, new { #placeholder = "Enter your email", #type = "email" })
</div>
<button type="submit" class="btn">Save email</button>
<div class='spinner'></div>
}
The spinner is not displayed initially (CSS):
.spinner {
display: none;
}
On form submit I show a spinner on the page:
$('.btn').on("click", function (event) {
event.preventDefault();
$('#my__form').submit();
$('.spinner').show();
});
My action is as follows:
[HttpPost]
[Route("email")]
public ActionResult Save(EmailViewModel model)
{
if (ModelState.IsValid)
{
//Do stuff with email
return RedirectToAction("action", "contoller");
}
return View(model);
}
Think of the above as pseudo-code that represents a more generic issue.
If a model state is invalid and the UI is updated in some way (in this case showing a spinner), what's the pattern or mechanism to reset the form?
For clarity, I am not talking here about standard data validation etc. I've tried having a hidden input on the form that I can populate using ModelState.AddModelError("spinner", false) and then acts as a 'switch' that javascript can read and then hide the spinner. This feels like a hack.
It feels like this is a common problem to which there is a common solution?
The hack you mentioned is really how it would be done using normal ASP.NET MVC.
There can be different implementations, such as storing the flag in a ViewBag instead. But the idea is the same.
You might be better off posting the form via AJAX, whose result might include a success flag and/or a list of validation errors. You can then manipulate the DOM in the submit handler via JavaScript based on this result.

Resending a Model resets values in asp.net mvc?

This is my View:
#model test2.Models.ChatModel
#{
ViewBag.Title = "Channel";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<center>
<h2>Channel: #Model.channelName</h2>
#{
foreach (string line in Model.chatLog) {
<div>#line</div>
}
}
<br />
#using (Html.BeginForm("sendMessage", "Home", FormMethod.Post)) {
#Html.TextBoxFor(model => model.message)
<button type="submit"> Send Message </button>
}
</center>
Here is my Controller:
public ActionResult sendMessage(ChatModel model) {
//send message somewhere
//this is not working
return RedirectToAction("Channel", "Home", new { channel = model.channelName });
//this is working
return RedirectToAction("Channel", "Home", new { channel = "test" });
}
The error happens in the redirectToAction method. Somehow "model.channelName" is empty, but #Model.channelName in my view is correctly displaying the channel name.
It looks like when you send a Model to a view, and "resend" this model back to a controller, the informations are lost.
Is there an easy way to solve this?
PS Step by step:
Model gets channelName
Model is send to view
View correctly displays data from model
adding message to Model
sending model to controller
model does NOT contain information from step 1
You need to include model.channelName in the form. Try adding a:
#Html.HiddenFor(model => model.channelName)
Anything not posted by the form, will be null in your model (including your chatlog)
Actually the values model properties should be rendered as input elements within the form that is posted back to controller action. The properties which are not included would loose their values.
What you can do is create a hidden field for those to post :
#using (Html.BeginForm("sendMessage", "Home", FormMethod.Post)) {
#Html.TextBoxFor(model => model.message)
#Html.HiddenFor(model => model.channelName)
<button type="submit"> Send Message </button>
}
You would need to add same way other properties too that are posting null at action and you need those for some processing.
Hope it helps.

Best way to handle add/view/delete on one page

What I want to do
I am very new to MVC.
I'm trying to create a page that allows users to perform the following actions on the same page:
View the list (table)
Add a new item (Filling the form and clicking the Add button should update the table)
Delete an item from the list (Clicking the Delete button in a row should update the table)
A simple example looks like this but I actually have two lists on one page (Fees and Costs):
Question
What would be the best way to achieve this?
Should I go with Dylan Beattie's method posted here which would look something like this?
public ActionResult MyAction(string submitButton, MyViewModel form)
{
switch (submitButton)
{
case "AddFee":
return (AddFee(form));
case "AddCost":
return (AddCost(form));
case "RemoveFee":
return (RemoveFee(form));
case "RemoveCost":
return (RemoveCost(form));
}
}
public ActionResult AddFee(MyViewModel form)
{
Fee newFee = ....; // Get entered data from `form`
_repository.InsertFee(newFee);
return View("Create"); //Back to the original page
}
Or is there any other recommended methods to handle this such as using JavaScript?
You could create the table as a partial view and re render this via ajax.
Wrap the partial view in a div and Wrap the form in #using (Ajax.BeginForm(.... and target the wrapper div. Your controller action that is targeted by the ajax request will need to return a partial view.
Here is a simple example
public class HomeController : Controller
{
public ActionResult Index()
{
MYvm vm = new MYvm() { id = 1, name = "This is my View Model" };
return View(vm);
}
public ActionResult DA(MYvm vm)
{
vm.name = "CHANGED";
return PartialView("Part", vm);
}
View:
#model MvcApplication1.Controllers.HomeController.MYvm
#{
ViewBag.Title = "Home Page";
}
#using (Ajax.BeginForm("DA", "Home", new AjaxOptions() { UpdateTargetId = "cont", HttpMethod = "Get" }))
{
<div>
Id: #Html.EditorFor(model => model.id)
</div>
<div>
Name: #Html.EditorFor(model => model.name)
</div>
<input type="submit" value="SubmitForm" />
}
<div id="cont">
#{Html.RenderPartial("part", Model);}
</div>
Partial View
#model MvcApplication1.Controllers.HomeController.MYvm
#{
ViewBag.Title = "part";
}
<h2>part</h2>
#Model.name
Should I go with [previous SO answer]
No. That answer was for a different scenario where the question had a form with two submit buttons that wanted to do two different actions (and wasn't even the accepted answer to that question).
Your sample screenshot indicates that some javascript/jquery and ajax would solve the issue cleanly.
As you're new to MVC, try to keep it relatively simple. Break up the page into separate parts:
the containing page
the edit form
the list with remove
the edit/list work independently and should be written in a way that they could be put on any other page - the page is just there to contain them and doesn't do much else (obviously your real page will contain more, but add those parts as separate components as well).
1 Create actions for your list and edit forms that return partialviews - just the parts that are needed for that view (self-contained)
controller:
[HttpGet]
public ActionResult AddCost()
{
var model = new Cost();
return PartialView(model);
}
[HttpPost]
public void AddCost(Cost model)
{
if (ModelState.IsValid) {
db.SaveCost(model);...
}
}
form Views/Home/AddCost.cshtml:
#using (Ajax.BeginForm(...
{
<div class='editor-label'>#Html.LabelFor(model=>model.Description)</div>
...etc...
}
I'll leave you to set the Ajax.BeginForm properties. But make sure the on-success calls reloadCostList() (see below)
controller
public ActionResult CostList()
{
var model = db.loadCosts(); ...
return PartialView(model);
}
list, Views/Home/CostList.cshtml
#model IEnumerable<ViewModels.Cost>
<table>
<thead>
<tr>
<th>Cost Description</th>
...
<tbody>
#foreach (var cost in Model.Costs)
{
<tr data-id='#cost.Id'>
<td>#Html.DisplayFor(x=>cost.Description)</td>
...
<td><a href='#' class='remove-button'>Remove</a></td>
}
...
2 Create an action + view for the main page with placeholder for the form and calls the list partial-action, eg:
<div id="body">
<div id="formWrapper">
#Html.Action("AddCost")
</div>
<div id="listWrapper">
#Html.Action("ListView")
</div>
</div>
if you already load the data for the page, you can pass it directly to the partial, but there's no need:
#Html.Partial("ListView", Model.Costs)
this allows you to refresh the list via an ajax call, something like:
function reloadCostList() {
$(".listWrapper").load("Home/CostList");
}
(ideally, $.ajax and add some fancy UI to indicate loading)
3 Add a remove action to your controller
[HttpPost]
public void RemoveCost(int id)
{
}
4 Wire up the Remove link
$(function() {
$(".remove-button").click(function() {
var id = $(this).closest("tr").attr("id");
$.post("/Home/RemoveCost/" + id, null, function() {
$(".listWrapper").load("Home/CostList");
// or reloadCostList(); from above
// or:
//$(".listWrapper tr[id=" + id + "]").hide();
});
});
}
rather than re-load the entire list, you could just remove the row (add some fancy UI like fade-out...)

Html.TextBoxFor not using value of new Model object

I am working on my first MVC Webapplication (using Razor and C#) and I have run across a strange behaviour.
I am editing a "line" of data and using ajax calls to submit and redisplay data. Everything works fine as far as changing existing data and storing it goes. Also if I just redisplay the "line" that was submitted no problems.
However, I want to display a "new" line with some of the values from the old line retained and the rest blanked out.
However, when I submit the new line object to the Partial View, the "blanked" out values aren't being picked up by the #Html.... helpers. But if I display the property of the Model directly it has the correct (blank) value.
Here are the relevant sections of my code:
Controller Method:
[HttpPost]
public ActionResult EditLineForm(SkuRequestLine ln)
{
SkuRequestLine newline = null;
try
{
if (ln.Store(true))
{
ViewData["prodcatdesc"] = DataConnection.GetProductCategory(ln.Category).description;
newline = new SkuRequestLine();
newline.Copy(ln);
newline.Line = DataConnection.NextSkuRequestLineNumber(ln.Request);
newline.Comments = "";
newline.Description = "";
newline.Vendorsku = "";
return PartialView("EditLineForm", newline); // this line is being executed.
}
else
{
return PartialView("EditLineForm", ln);
}
}
catch (Exception ex)
{
List<string> msgs = new List<string>();
while (ex != null)
{
msgs.Add(ex.Message);
ex = ex.InnerException;
}
return PartialView("EditLineForm", ln);
}
}
Razor Code:
#model Sku_Management.Models.SkuRequestLine
#using (Ajax.BeginForm("EditLineForm", "SkuRequest", new AjaxOptions { OnSuccess = "UpdateLineList" }))
{
.
.
.
<tr>
<td>
<span class="editor-label">
#Html.LabelFor(model => model.Description)
</span>
</td>
<td colspan="5">
<span class="editor-field">
#Html.TextBoxFor(model => model.Description, new { #class = "fortywide" }) // Displays the Description from the edited Line passed in. Not what what Model.Description is.
#Html.ValidationMessageFor(model => model.Description)
</span>
<span>|#Model.Description|</span> // Displays "||" which is what it should be since Model.Description is blank.
</td>
</tr>
The only thing I can think of is that model => model.Description is using a cached version of the Model not the new Model passed into the PartialView call.
I have spent the day searching for anything even similar on the web but I can't find anything that even begins to describe this behavior.
Has anyone else encountered this and knows what I am dong wrong?
Thanks
This is because the HTMLHelpers look to the ModelState for values before using the Model.
You'll have to clear the ModelState entries to get this to work.

Categories

Resources