1
\$\begingroup\$

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 from DataAnnotations 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 in View 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 force ajax to invoke error function? I found it more useful to respond with Json 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>
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.