General purpose of this training project is to control sound volume on remote machine (which exposes ISystemControlClient
through web service). It's created using AspNetCore 2.0 with MVC6.
What I'd like to learn is:
Are there built in features in asp mvc 6 that simplify async calls to the controller and then update page elements (I believe there was something like
Ajax.ActionLink
in previous versions of the framework, is there something similar)?Could I possibly sumbit entire
Model
to benefit fromDataAnnotations
for async validation messages using built in functionality (like when you're using regular form)?Would it be better to store scripts small like the one I have in
Scripts
(preserving views structure) folder and include them inView
or there's some good reason to keep scripts that interact with specific page elements within the view?Is responding with
HttpStatusCode
only way to forceajax
to invokeerror
function? I found it more useful to respond withJson
message because that allows me to return error message instead of hardcoding it into view, but I really prefer specifying function that is called on error over validating json response in success function.All kind of good practices I might violated.
Model:
namespace HMS.WebClient.Models.Sound
{
public class Volume
{
[Display(Name = "Current Volume")]
public int Value { get; set; }
public Volume()
{
}
public Volume( int value )
{
Value = value;
}
}
}
Controller:
namespace HMS.WebClient.Controllers
{
public class SoundController : Controller
{
private readonly ISystemControlClient _client;
public SoundController( ISystemControlClient systemControlClient )
{
_client = systemControlClient;
_client.Open();
}
[HttpGet]
public async Task<ActionResult> Index()
{
var currentVolume = await _client.GetCurrentVolumeAsync();
var volume = new Volume( currentVolume );
return View( volume );
}
[HttpPost]
public async Task<IActionResult> SetVolumeAsync( int value )
{
try
{
await _client.SetVolumeAsync( value );
return StatusCode( (int) HttpStatusCode.Accepted );
}
catch ( FaultException<ExceptionDetail> e ) when ( Type.GetType( e.Detail.Type ) == typeof(ArgumentOutOfRangeException) )
{
return new StatusCodeResult( (int) HttpStatusCode.Forbidden );
}
}
protected override void Dispose( bool disposing )
{
if ( disposing )
{
_client?.Close();
}
base.Dispose( disposing );
}
}
}
Controller tests:
namespace Test.WebClient.Sound
{
public class SetVolumeTests : SoundControllerTestBase
{
private const int ValidVolume = 99;
[Fact]
public async Task ShouldReturnAcceptedCode_WhenValueIsAccepted()
{
var result = await Controller.SetVolumeAsync( ValidVolume );
result.ShouldBeStatusCodeResult( HttpStatusCode.Accepted );
}
[Fact]
public async Task ShouldReturnErrorMessage_WhenValueIsOutOfRange()
{
const int invalidVolume = 101;
SetupTheException( invalidVolume );
var result = await Controller.SetVolumeAsync( invalidVolume );
result.ShouldBeStatusCodeResult( HttpStatusCode.Forbidden );
}
[Fact]
public async Task ShouldSetVolumeToGivenValue()
{
await Controller.SetVolumeAsync( ValidVolume );
SystemControlMock.Verify( x => x.SetVolumeAsync( ValidVolume ), Times.Once );
}
private void SetupTheException( int invalidVolume )
{
var exception = new FaultException<ExceptionDetail>(
new ExceptionDetail( new ArgumentOutOfRangeException() ),
new FaultReason( "error" ),
new FaultCode( "error" ),
string.Empty );
SystemControlMock.Setup( x => x.SetVolumeAsync( invalidVolume ) ).Throws( exception );
}
}
}
View (Views/Sound/Index.cshtml):
@model HMS.WebClient.Models.Sound.Volume
<h2>Volume Settings</h2>
<hr/>
<dl class="dl-horizontal">
<dt>
<label asp-for="Value" class="control-label col-sm-2"></label>
</dt>
<dd id="currentVolumeValue">
@Html.DisplayFor( x => Model.Value )
</dd>
</dl>
<div>
<input asp-for="Value" type="range" min="0" max="100" onchange="changeVolume(this.value)"/>
<span id="errorMessage" class="text-danger"></span>
</div>
<script type="text/javascript">
function changeVolume(value) {
$('#errorMessage').html('');
var data = { value: value };
$.ajax({
url: '@Url.Action("SetVolumeAsync")',
type: 'POST',
data: data,
datatype: 'json',
success: function() {
$('#currentVolumeValue').html(value);
},
error: function() {
$('#errorMessage').html("Invalid value.");
}
});
}
</script>