Reputation:
I had an assignment to implement a WCF Service and call it from a client of any type. I already have some experience with console, winform, WPF apps, so I wanted to use this as a chance to learn ASP.NET MVC.
I know that I'm not properly implementing the "M" part of it yet, but I'm wondering what the correct way of processing data on a form in order to update show a result would be instead of what I have (working) so far:
View:
@using (Html.BeginForm("UseSimpleMathClient", "Home"))
{
<div style="width:450px">
<table style="width:100%">
<tr><td><h4>@Html.Label("First number", new { @for = "num1" })</h4></td> <td> @Html.TextBox("num1", ViewData["num1"])</td></tr>
<tr><td><h4>@Html.Label("Second number", new { @for = "num2" })</h4></td> <td> @Html.TextBox("num2", ViewData["num2"])</td></tr>
<tr>
<td colspan="2">
<h2 style="text-align:center">
<input type="submit" name="operation" value="+" />
<input type="submit" name="operation" value="-" />
<input type="submit" name="operation" value="*" />
<input type="submit" name="operation" value="÷" />
</h2>
</td>
</tr>
<tr>
<td colspan="2"><b>@Html.Label("Result")</b> @Html.TextBox("result", ViewData["result"], new {disabled = "disabled", style="min-width:80%;text-align:center" })</td>
</tr>
</table>
</div>
}
Controller:
public ActionResult USeSimpleMathClient(char operation)
{
float num1, num2;
float? result = null;
if (!float.TryParse(Request.Form[0], out num1) || !float.TryParse(Request.Form[1], out num2))
{
ViewData["result"] = "Please enter valid numbers before selecting an operation.";
}
else
{
switch (operation)
{
case '+':
result = mathClient.Add(num1, num2);
break;
case '-':
result = mathClient.Subtract(num1, num2);
break;
case '*':
result = mathClient.Multiply(num1, num2);
break;
case '÷':
if (num2 != 0)
result = mathClient.Divide(num1, num2);
break;
default:
break;
}
ViewData["result"] = result != null ? String.Format("{0} {1} {2} = {3}", num1, operation, num2, result) : "You can't divide by zero!";
}
ViewData["num1"] = Request.Form[0];
ViewData["num2"] = Request.Form[1];
return View("Index");
}
Upvotes: 2
Views: 5967
Reputation: 726
Ok, so I took your code and did a quick implementation of your controller action using a viewmodel. Note that I have data annotation attributes at the top of some of the view model properties just so we don't have to manually verify that num1 and num2 are actually numbers or not.
Viewmodel:
public class SimpleMathVM
{
[Required(ErrorMessage = "Please enter valid numbers before selecting an operation.")]
[Display(Name = "First number")]
public float Num1 { get; set; }
[Required(ErrorMessage = "Please enter valid numbers before selecting an operation.")]
[Display(Name = "Second number")]
public float Num2 { get; set; }
public string Result { get; set; }
public char Operation { get; set; }
}
Controller (both GET and POST actions):
public ActionResult USeSimpleMathClient()
{
return View("Index");
}
[HttpPost]
public ActionResult USeSimpleMathClient(SimpleMathVM viewModel)
{
//I moved the checking for zero up here, that way you could immediately return the view.
//I kept your version where you populate the Result textbox with the error message,
//but really you should probably only add the error message to the ModelState.
if (viewModel.Num2 == 0 && viewModel.Operation == '÷')
{
ModelState.AddModelError(string.Empty, "You can't divide by zero!");
viewModel.Result = "You can't divide by zero!";
//You can pick which one of the above two lines work better for you.
//Usually adding error messages to the ModelState is the way to go.
}
if (!ModelState.IsValid)
{
return View("Index", viewModel);
}
switch (viewModel.Operation)
{
case '+':
viewModel.Result = mathClient.Add(viewModel.Num1, viewModel.Num2).ToString();
break;
case '-':
viewModel.Result = mathClient.Subtract(viewModel.Num1, viewModel.Num2).ToString();
break;
case '*':
viewModel.Result = mathClient.Multiply(viewModel.Num1, viewModel.Num2).ToString();
break;
case '÷':
viewModel.Result = mathClient.Divide(viewModel.Num1, viewModel.Num2).ToString();
break;
default:
break;
}
return View("Index", viewModel);
}
View:
//must bring in the View Model so you can access the MVC lambda helpers below
@model WebApplication11.Models.SimpleMathVM
@using (Html.BeginForm("UseSimpleMathClient", "Home"))
{
//You can use ValidationSummary to display object level model errors,
//such as the division by zero error we directly added to the ModelState
//in the controller
@Html.ValidationSummary(true);
<style type="text/css">
.navbar {
display: none !important;
}
body {
padding: 0px 0px 0px 0px !important;
}
</style>
<div style="width:450px">
<table style="width:100%">
<tr><td><h4>@Html.LabelFor(x => x.Num1)</h4></td> <td> @Html.TextBoxFor(x => x.Num1)</td><td> @Html.ValidationMessageFor(x => x.Num1)</td></tr>
<tr><td><h4>@Html.LabelFor(x => x.Num2)</h4></td> <td> @Html.TextBoxFor(x => x.Num2)</td><td> @Html.ValidationMessageFor(x => x.Num2)</td></tr>
<tr>
<td colspan="2">
<h2 style="text-align:center">
<input type="submit" name="operation" value="+" />
<input type="submit" name="operation" value="-" />
<input type="submit" name="operation" value="*" />
<input type="submit" name="operation" value="÷" />
</h2>
</td>
</tr>
<tr>
<td colspan="2">
<b>@Html.LabelFor(x => x.Result)</b> @Html.TextBoxFor(x => x.Result, new { disabled = "disabled", style = "min-width:80%;text-align:center" })
@Html.ValidationMessageFor(x => x.Result)
</td>
</tr>
</table>
</div>
}
Note that now your form fields can be strongly typed to your view model in your form. The model binding system of ASP.NET MVC simply populates your posted form fields into your view model instance on the post controller action. You don't even have to do the manual work of matching up the fields. And you don't have to use Viewbag anymore too.
Also note that on the post controller action I put a check if the second number is a zero, adding the error directly to the ModelState, and also to the Results view, so you'll see the error message displayed twice on the view. You were originally putting the error message in the results view (so I kept this), but you might consider adding any error handling messages solely through the ModelState object instead of populating on the results textbox. You could also check for the division by zero in your WCF service. Another option is to create a custom data annotation to check for division by zero. Hope this helps.
Upvotes: 4