Tag Archives: ViewModel

Asp.Net Core 2.0 – Passing data into a Model Using DI

Imagine you have an Asp.Net Core web page, and you would like to edit some data in a form, but you’d like to default that data to something (maybe initially specified in the Web.Config).

I’m sure there’s dozens of ways to achieve this; but this is one.

Let’s start with a bog standard MVC web app:

Step one is to define a model in which to hold your data; here’s mine:

public class CurrentAppData
{
    public string DataField1 { get; set; }
}

Let’s register that in the IoC container:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });
 
 
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
 
    services.AddTransient<CurrentAppData, CurrentAppData>(
        a => new CurrentAppData()
        {
            DataField1 = "test value"
        });

Next thing we’ll need is a View, and a corresponding view model to edit our data; here’s the view:

@model EditDataViewModel
@{
    ViewData["Title"] = "Edit Data";
}
 
<h2>@ViewData["Title"]</h2>
 
<div>
    <label>Change data here:</label>
    <input type="text" asp-for="EditData.DataField1" />
 
</div>

And now the view model (that is, the model that is bound to the view):

public class EditDataViewModel
{
    public EditDataViewModel(CurrentAppData editData)
    {
        EditData = editData;
    }
    public CurrentAppData EditData { get; set; }
}

The final step here is to adapt the controller so that the CurrentAppData object is passed through the controller:

public class EditDataController : Controller
{
    private readonly CurrentAppData _currentAppData;
 
    public EditDataController(CurrentAppData currentAppData)
    {
        _currentAppData = currentAppData;
    }
 
    public IActionResult EditData()
    {
        return View(new EditDataViewModel(_currentAppData));
 
 
    }
}

That works as far as it goes, and we now have the data displayed on the screen:

The next step is to post the edited data back to the controller; let’s change the HTML slightly:

<form asp-action="UpdateData" asp-controller="EditData" method="post" enctype="multipart/form-data">
    <label>Change data here:</label>
    <input type="text" asp-for="EditData.DataField1" />
    <br />
    <button type="submit" class="btn-default">Submit Changes</button>
</form>

We’ve added a submit button, which should cause the surrounding form element to execute whichever “method” it’s been told to (in this case, POST). It will look for an action on the controller called UpdateData, so we should create one:

public IActionResult UpdateData(EditDataViewModel editDataViewModel)
{
    System.Diagnostics.Debug.WriteLine(editDataViewModel.EditData.DataField1);
    return View("EditData", editDataViewModel);
}

Here, we’re accepting the EditDataViewModel from the view. However; when we run this, we get the following error:

Error:

InvalidOperationException: Could not create an instance of type ‘WebApplication14.Models.EditDataViewModel’. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the ‘editDataViewModel’ parameter a non-null default value.

Let’s first implement a fix for this, and then go into the whys and wherefores. The fix is actually quite straightforward; simply give the view model a parameterless constructor:

public class EditDataViewModel
{
    public EditDataViewModel()
    {
        
    }
 
    public EditDataViewModel(CurrentAppData editData)
    {
        EditData = editData;
    }
    public CurrentAppData EditData { get; set; }
}

The problem that we had here is that the `EditDataViewModel` that is returned to UpdateData is a new instance of the model. We can prove this by changing our code slightly:

Here, we’ve added a field called TestField1 to the model, and populated it just before we pass the model to the view; and on the post back, it’s gone. I’m not completely sure why the view model can’t be created by the middleware in the same way that the controller is; but that’s the subject of another post.

Finally, show the value back to the screen

To wrap up, we’ll just show the same value back to the screen; we’ll add an extra value to the model:

public class CurrentAppData
{
    public string DataField1 { get; set; }
 
    public string DisplayField1 { get; set; }
}

And we’ll just display it in the view:

<form asp-action="UpdateData" asp-controller="EditData" method="post" enctype="multipart/form-data">
    <label>Change data here:</label>
    <input type="text" asp-for="EditData.DataField1" />
    <br />
    <button type="submit" class="btn-default">Submit Changes</button>
    <br />
    <label>@Model.EditData.DisplayField1</label>
    <br />
</form>

Finally, we’ll copy that value inside the controller (obviously, this is simulating something meaningful happening), and then display the result:

public IActionResult UpdateData(EditDataViewModel editDataViewModel)
{
    editDataViewModel.EditData.DisplayField1 = editDataViewModel.EditData.DataField1;
    return View("EditData", editDataViewModel);
}

Let’s see what that looks like:

Updating a View Model from Inside a Collection

My last post suggested a concept of having a `Selectable` Model, which effectively wraps a model in a class with a generic item and an `IsSelected` property. This does, however, present a slight issue. What if you want to update the view model, based on the item being selected, or de-selected?

Say, for example, that from my previous post, you had a property on the Person class called Wages, and wanted to display the total wages of all the selected people. You would have defined `TotalCostSummary` in the ViewModel, and bound it in the View.

My solution is to pass a delegate into the Selectable item. Here’s how I did it (this doesn’t really make any sense without referring back to the first post).

SelectableItem Model

In the `SelectableItem`, define a new property like so:

        private Action _updateMethod = null;
        public Action IsSelectedChangedMethod
        {
            get { return _updateMethod; }
            set
            {
                _updateMethod = value;
            }
        }

And change `IsSelected` to looks like this:

        private bool _isSelected;
        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                _isSelected = value;                
                RaisePropertyChanged();
                
                if (_updateMethod != null)
                    _updateMethod();
            }
        }

ViewModel

        private void UpdateTotalCost()
        {
            _summary = SelectablePeople.Where(a => a.IsSelected).Sum(s => s.Item.Wages);
            RaisePropertyChanged(() => TotalCostSummary);
        }

Next, change the code to define the list:

List<Person> population = GetPeople();
SelectablePeople = new ObservableCollection<SelectableItem<Person>>(population
	.Select(n => new SelectableItem<Person>() 
	{ 
		Item = n, IsSelected = false, IsSelectedChangedMethod = (() => UpdateTotalCost())  
	}).ToList());

Conclusion

The net effect is that the changes now made inside the model can bubble up to the ViewModel, but the separation of the two has been maintained.

MVVM Cross Basics – Passing Complex Parameters During Navigation

Having covered basic navigation, I found that I needed to pass my game state from view-model to view-model. I initially thought that I could simply do this as follows:

    private void CallVM2()
    {
        MyObj newObj = new MyObj();
        IMyService myService = new MyService();

		Dictionary<string, object> p = new Dictionary<string, object>()
        {
            {"MyObj", newObj},
            {"MyService", myService}
        };
		ShowViewModel<ViewModel2>(p);
    }

The code for ViewModel2:

    public void Init(Dictionary<string, object> p)
    {
    }

Anyway, it turns out you can’t. Init does get fired, but if you pass anything more complex than a string, it just bins the parameter.

The answer appears to be to create a class and to serialise it using JSON.NET:

http://stackoverflow.com/questions/19058173/passing-complex-navigation-parameters-with-mvvmcross-showviewmodel

After implementing this suggestion, I ended up with something like this:

    public abstract class BaseViewModel : MvxViewModel
    {
        private const string ParameterName = "parameter";

        public BaseViewModel()
        {
        }

        protected void ShowViewModel<TViewModel>(object parameter) where TViewModel : IMvxViewModel
        {
            var text = Mvx.Resolve<IMvxJsonConverter>().SerializeObject(parameter);
            base.ShowViewModel<TViewModel>(text);
        }

        public void Init(string parameter)
        {
            if (parameter == null || parameter.Length == 0) return;

            IMvxJsonConverter converter = Mvx.Resolve<IMvxJsonConverter>();
            NavigationParameter deserialized = converter.DeserializeObject<NavigationParameter>(parameter);
            RealInit(deserialized);
            
        }
    }

If you don’t register the converter in the IoC container (as I initially didn’t), then you’ll get this error:

A first chance exception of type ‘Cirrious.CrossCore.Exceptions.MvxIoCResolveException’ occurred in Cirrious.CrossCore.DLL

Additional information: Failed to resolve type Cirrious.CrossCore.Platform.IMvxJsonConverter

The fix:

    public abstract class BaseViewModel : MvxViewModel
    {
        private const string ParameterName = "parameter";

        public BaseViewModel()
        {
            Mvx.RegisterType<IMvxJsonConverter, MvxJsonConverter>();
        }

Now, if you override the `RealInit` method, you’ll see that the parameters are, indeed, available. However, I had a further problem; this time being with JSON.NET. As you can see from the above structure, it’s a dictionary of objects. So, when running this, it will certainly pass through the object, but it’ll just be the serialised string.

And…?

I’m pretty sure that I could get around this using a combination of generics and a custom Javascript serialisation library, but I’ve got a game to write, so I’m going to stick with declaring the Navigation parameter as follows:

   public class NavigationParameter
    {
        public MyObject ObjectParameter { get; set; }
        public MyService ServiceParameter { get; set; } 
    }

It’s worth bearing in mind that you can’t use interfaces either in the parameters; JSON.NET needs a concrete class.