Let's say I have complex view model with a lot of data such as lists of countries, products, categories etc. for which I need to fetch from the database every time I create the ViewModel.
The main problem I want to fix is that when I handle POST actions and some TestModel
was posted with incorrect values, which causes ModelState.IsValid
to be false
, then I have to return the same view with currently posted model. This forces me to get my list of categories again, since I was doing that in the GET action. This adds a lot of duplicated code in controller and I want to remove it. Currently I am doing the following:
My model and view models:
Model, entity stored in the database:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Category> SubCategories { get; set; }
}
View models:
public class CategoryModel
{
public int Id { get; set; }
public string Name { get; set; }
}
public class TestModel
{
[Required]
[MaxLength(5)]
public string Text { get; set; }
public int SelectedCategory { get; set; }
public IEnumerable<CategoryModel> Categories { get; set; }
public SelectList CategoriesList
{
get
{
var items = Categories == null || !Categories.Any()
? Enumerable.Empty<SelectListItem>()
: Categories.Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.Name
});
return new SelectList(items, "Value", "Text");
}
}
}
My controller:
public class HomeController : Controller
{
private readonly Repository _repository = ObjectFactory.GetRepositoryInstance();
public ActionResult Index()
{
var model = new TestModel
{
Categories = _repository.Categories.Select(c => new CategoryModel
{
Id = c.Id,
Name = c.Name
})
};
return View(model);
}
[HttpPost]
public ActionResult Index(TestModel model)
{
if (ModelState.IsValid)
{
return RedirectToAction("Succes");
}
model.Categories = _repository.Categories.Select(c => new CategoryModel
{
Id = c.Id,
Name = c.Name
});
return View(model);
}
public ActionResult Succes()
{
return View();
}
}
I want to remove duplicated Categories fetching and mapping, basically this code:
.Categories = _repository.Categories.Select(c => new CategoryModel
{
Id = c.Id,
Name = c.Name
})
from controller. Also I want to remove ModelState
validity check, I want to execute the action only if ModelState.IsValid
to keep controller code AS CLEAN AS POSSIBLE. So far I have the following solution for removing ModelState
validity check:
Create custom ValidateModelAttribute
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var viewData = filterContext.Controller.ViewData;
if(viewData.ModelState.IsValid) return;
viewData.Model = filterContext.ActionParameters["model"];
filterContext.Result = new ViewResult
{
ViewData = viewData,
};
}
}
Now model is validated before the action executes. In case of validation errors, we use same view with the same recently posted model. Therefore, the controller POST action looks like this:
[HttpPost]
[ValidateModelAttribute]
public ActionResult Index(TestModel model)
{
// Do some important stuff with posted data
return RedirectToAction("Success");
}
This is nice, but now my Categories
property of my TestModel
is empty, because I have to fetch the categories from the database, and map them accordingly. So is it OK to modify my view model to look something like this:
public class TestModel
{
private readonly Repository _repository = ObjectFactory.GetRepositoryInstance();
...
public int SelectedCategory { get; set; }
public IEnumerable<CategoryModel> Categories {
get
{
return _repository.Categories.Select(c => new CategoryModel
{
Id = c.Id,
Name = c.Name
});
}
}
...
}
This will allow us to have very clean controller, but wouldn't it cause some kind of performance or architectural issues? Wouldn't it break the single responsibility principle for view models? Should ViewModels be responsible for fetching data it needs?
The repository class isolates the data sources from the rest of the app and provides a clean API for data access to the rest of the app. Using a repository class ensures this code is separate from the ViewModel class, and is a recommended best practice for code separation and architecture.
What is Repository in Android's MVVM architecture? Repository is a class which purpose is to provide a clean API for accessing data. What that means is that the Repository can gather data from different data sources(different REST APIs, cache, local database storage) and it provides this data to the rest of the app.
LiveData objects should not live in the repository. // noticeable jank in the UI! If you need to use streams of data in other layers of your app, consider using Kotlin Flows and then converting them to LiveData in the ViewModel using asLiveData() . Learn more about using Kotlin Flow with LiveData in this codelab.
VIEW: ( Platform Specific Code – USER INTERFACE ) What the user sees, The Formatted data. VIEWMODEL: ( Reusable Code – LOGIC ) Link between Model and View OR It Retrieves data from Model and exposes it to the View. This is the model specifically designed for the View.
It's not ok. the view model should be mainly a DTO populated by a service/query or even the controller. There was no problem with the previous version, your controller is just a couple of lines of code.
But your repository is not really a repository, it's a an ORM. A proper repository (well here it would be just some query object) would return directly the list of Categories for the view model.
About your auto validation attribute, don't reinvent the wheel, someone else (in this case me) did it before .
No, you shouldn't put repository reference and logic into the view models. I suppose the only thing you need is to be able to rebuild the model if the validation fails. You can try one of the automated ModelState validation, for example:
http://benfoster.io/blog/automatic-modelstate-validation-in-aspnet-mvc
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With