Asp.net mvc what is the best practice of rebuilding ViewModel? - asp.net-mvc

On POST , if validation failed and before sending back the ViewModel to the same View with Model State errors, do you rebuild ViewModel for all SelectLists, ReadOnly fields etc?
right now I have separate methods for Fill First Time(for GET Edit-Method) / Rebuild ViewModels from domain objects, what is the best practice so I can be DRY and also not have to change two methods any time I add a new readonly property to ViewModel?
My Solution: Followed this Pattern
Followed pattern suggested here: https://stackoverflow.com/a/2775656/57132
In IModelBuilder Implementation
Build(..)
{
var viewModel = new ViewModel();
// and Fill all Non-ReadOnly fields
...
...
call CompleteViewModel(viewModel)
}
CompleteViewModel(ViewModel viewModel)
{
//Fill all ReadOnly & SelectLists
...
}
The reason I went with this solution is because I don't want to store stuff on server to retrieve across the HTTP Requests

I don't rebuild it, because I don't stay at POST. I follow POST-REDIRECT-GET pattern, so if I post to /User/Edit/1 using POST HTTP method, I get redirected to /User/Edit/1 uasing GET.
ModelState is transferred to TempData to follow Post-Redirect-Get and be availabe at GET call. View model is built in one place, at GET call. Example:
[HttpPost]
[ExportModelStateToTempData]
public ActionResult Edit(int id, SomeVM postedModel)
{
if (ModelState.IsValid) {
//do something with postedModel and then go back to list
return RedirectToAction(ControllerActions.List);
}
//return back to edit, because there was an error
return RedirectToAction(ControllerActions.Edit, new { id });
}
[ImportModelStateFromTempData]
public ActionResult Edit(int id)
{
var model = //create model here
return View(ControllerActions.Edit, model);
}
This is code for attributes importing/exporting ModelState:
public abstract class ModelStateTempDataTransferAttribute : ActionFilterAttribute
{
protected static readonly string Key = typeof(ModelStateTempDataTransferAttribute).FullName;
}
public class ExportModelStateToTempDataAttribute : ModelStateTempDataTransferAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Only export when ModelState is not valid
if (!filterContext.Controller.ViewData.ModelState.IsValid)
{
//Export if we are redirecting
if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
{
filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
}
}
base.OnActionExecuted(filterContext);
}
}
public class ImportModelStateFromTempDataAttribute : ModelStateTempDataTransferAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;
if (modelState != null)
{
//Only Import if we are viewing
if (filterContext.Result is ViewResult)
{
filterContext.Controller.ViewData.ModelState.Merge(modelState);
}
else
{
//Otherwise remove it.
filterContext.Controller.TempData.Remove(Key);
}
}
base.OnActionExecuted(filterContext);
}
}

The simplest solution would be to pass in you viewModel to the method and account for null
private MyViewModel BuildViewModel(MyViewModel model = null)
{
model = model ?? new MyViewModel();
model.ReadOnlyList = new .....
.
.
return model;
}
for Create:
var model = BuildViewModel();
for rebuild:
model = buildViewModel(model);

I like #LukLed's answer above - it looks very interesting. If you want another option, here's what I currently do.
In my service layer, I have a method to build my view model. I call that on GET and return the the view model to the view. On POST, I build the model from the incoming ID and then TryUpdateModel(model). From there, you can do whatever you like (save, check model state, etc.). With this method, you only have 1 build method and only have to update it once if your model changes (i.e. add/remove properties in the future, etc.).
[HttpGet]
public ActionResult AssessFocuses(int apaID)
{
var model = this.apaService.BuildAssessFocusesViewModel(apaID);
return this.View(model);
}
[HttpPost]
public ActionResult AssessFocuses(int apaID, string button)
{
var model = this.apaService.BuildAssessFocusesViewModel(apaID);
this.TryUpdateModel(model);
switch (button)
{
case ButtonSubmitValues.Back:
case ButtonSubmitValues.Next:
case ButtonSubmitValues.Save:
case ButtonSubmitValues.SaveAndClose:
{
try
{
this.apaService.SaveFocusResults(model);
}
catch (ModelStateException<AssessFocusesViewModel> mse)
{
mse.ApplyTo(this.ModelState);
}
if (!this.ModelState.IsValid)
{
this.ShowErrorMessage(Resources.ErrorMsg_WEB_ValidationSummaryTitle);
return this.View(model);
}
break;
}
default:
throw new InvalidOperationException(string.Format(Resources.ErrorMsg_WEB_InvalidButton, button));
}
switch (button)
{
case ButtonSubmitValues.Back:
return this.RedirectToActionFor<APAController>(c => c.EnterRecommendationsPartner(model.ApaID));
case ButtonSubmitValues.Next:
return this.RedirectToActionFor<APAController>(c => c.AssessCompetenciesPartner(model.ApaID));
case ButtonSubmitValues.Save:
this.ShowSuccessMessage(Resources.Msg_WEB_NotifyBarSuccessGeneral);
return this.RedirectToActionFor<APAController>(c => c.AssessFocuses(model.ApaID));
case ButtonSubmitValues.SaveAndClose:
default:
return this.RedirectToActionFor<UtilityController>(c => c.CloseWindow());
}
}

Related

How to code a Polymorphic Model Binder and Provider in MVC 6

This question has been asked before on SO and elsewhere in the context of MVC3 and there are bits and bobs about it related to ASP.NET Core RC1 and RC2 but niot a single example that actually shows how to do it the right way in MVC 6.
There are the following classes
public abstract class BankAccountTransactionModel {
public long Id { get; set; }
public DateTime Date { get; set; }
public decimal Amount { get; set; }
public readonly string ModelType;
public BankAccountTransactionModel(string modelType) {
this.ModelType = modelType;
}
}
public class BankAccountTransactionModel1 : BankAccountTransactionModel{
public bool IsPending { get; set; }
public BankAccountTransactionModel1():
base(nameof(BankAccountTransactionModel1)) {}
}
public class BankAccountTransactionModel2 : BankAccountTransactionModel{
public bool IsPending { get; set; }
public BankAccountTransactionModel2():
base(nameof(BankAccountTransactionModel2)) {}
}
In my controller I have something like this
[Route(".../api/[controller]")]
public class BankAccountTransactionsController : ApiBaseController
{
[HttpPost]
public IActionResult Post(BankAccountTransactionModel model) {
try {
if (model == null || !ModelState.IsValid) {
// failed to bind the model
return BadRequest(ModelState);
}
this.bankAccountTransactionRepository.SaveTransaction(model);
return this.CreatedAtRoute(ROUTE_NAME_GET_ITEM, new { id = model.Id }, model);
} catch (Exception e) {
this.logger.LogError(LoggingEvents.POST_ITEM, e, string.Empty, null);
return StatusCode(500);
}
}
}
My client may post either BankAccountTransactionModel1 or BankAccountTransactionModel2 and I would like to use a custom model binder to determine which concrete model to bind based on the value in the property ModelType which is defined on the abstract base class BankAccountTransactionModel.
Thus I have done the following
1) Coded up a simple Model Binder Provider that checks that the type is BankAccountTransactionModel. If this is the case then an instance of BankAccountTransactionModelBinder is returned.
public class BankAccountTransactionModelBinderProvider : IModelBinderProvider {
public IModelBinder GetBinder(ModelBinderProviderContext context) {
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) {
var type1 = context.Metadata.ModelType;
var type2 = typeof(BankAccountTransactionModel);
// some other code here?
// tried this but not sure what to do with it!
foreach (var property in context.Metadata.Properties) {
propertyBinders.Add(property, context.CreateBinder(property));
}
if (type1 == type2) {
return new BankAccountTransactionModelBinder(propertyBinders);
}
}
return null;
}
}
2) Coded up the BankAccountTransactionModel
public class BankAccountTransactionModelBinder : IModelBinder {
private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;
public BankAccountTransactionModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders){
this._propertyBinders = propertyBinders;
}
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
// I would like to be able to read the value of the property
// ModelType like this or in some way...
// This does not work and typeValue is...
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
// then once I know whether it is a Model1 or Model2 I would like to
// instantiate one and get the values from the body of the Http
// request into the properties of the instance
var model = Activator.CreateInstance(type);
// read the body of the request in some way and set the
// properties of model
var key = some key?
var result = ModelBindingResult.Success(key, model);
// Job done
return Task.FromResult(result);
}
}
3) Lastly I register the provider in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => {
options.ModelBinderProviders.Insert(0, new BankAccountTransactionModelBinderProvider());
options.Filters.Add(typeof (SetUserContextAttribute));
});
The whole thing seems OK in that the provider is actually invoked and the same is the case for the model builder. However, I cannot seem to get anywhere with coding the logic in BindModelAsync of the model binder.
As already stated by the comments in the code, all that I'd like to do in my model binder is to read from the body of the http request and in particular the value of ModelType in my JSON. Then on the bases of that I'd like to instantiate either BankAccountTransactionModel1 or BankAccountTransactionModel and finally assign values to the property of this instance by reading them of the JSON in the body.
I know that this is a only a gross approximation of how it should be done but I would greatly appreciate some help and perhaps example of how this could or has been done.
I have come across examples where the line of code below in the ModelBinder
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
is supposed to read the value. However, it does not work in my model binder and typeValue is always something like below
typeValue
{}
Culture: {}
FirstValue: null
Length: 0
Values: {}
Results View: Expanding the Results View will enumerate the IEnumerable
I have also noticed that
bindingContext.ValueProvider
Count = 2
[0]: {Microsoft.AspNetCore.Mvc.ModelBinding.RouteValueProvider}
[1]: {Microsoft.AspNetCore.Mvc.ModelBinding.QueryStringValueProvider}
Which probably means that as it is I do not stand a chance to read anything from the body.
Do I perhaps need a "formatter" in the mix in order to get desired result?
Does a reference implementation for a similar custom model binder already exist somewhere so that I can simply use it, perhaps with some simple mods?
Thank you.

How to return an IHttpActionResult inside an MVC Controller?

I'm tinkering with an MVC Controller, having lifted some code out of an ApiController:
public class LoginController : Controller
{
public async Task<IHttpActionResult> Login(LoginModel model)
{
var success = await _accountClient.Login(model);
if (!success)
{
return new UnauthorizedResult(Enumerable.Empty<AuthenticationHeaderValue>(), new HttpRequestMessage());
}
return new OkResult(new HttpRequestMessage());
}
}
This just doesn't look or feel right, particularly the part where I'm creating a new HttpRequestMessage; I'm pretty sure that should somehow come from the incoming Request, but I can't figure out where that's supposed to come from.
Also, how do you create the IEnumerable<AuthenticationHeaderValue> object for the challenges parameter to the UnauthorizedResult constructor?
You can add headers to your Response object as
Response.AddHeader("Token","your-generated-token");
Also you can change your action method as
public async Task<HttpStatusCodeResult> Login(LoginModel model)
{
var success = await _accountClient.Login(model);
if (!success)
{
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
}
return new HttpStatusCodeResult(HttpStatusCode.Ok);
}

ASP.NET MVC URL routing

I have a question, can this be done using ASP.NET MVC (Razor, and MapRoute)?
Example
Category/ - calls controller Category and Index function
Category/{State_name} - calls controller Category and Cities(State_id) function, returns all cities inside that state.
So URL is displaying state name , but Cities function receive state id?
Yes u can, try
public class CategoryController : Controller {
// GET: /Category/
// OR
// GET: /Category/State_name
public ActionResult Index(string State_name) {
if (!string.IsNullOrWhiteSpace(State_name)) {
int? State_id = FindStateIdFromStateName(State_name); // retrive state_id from datastore where state_name == State_name
if (State_id.HasValue) { // means the currect state_name passed to action
var model = FindStateById(State_id.Value);
return View("Cities", model); // will renders Cities.cshtml with specified model
} else {
// means the specified state_name not found! u can do something else, or allow continue bellow lines
}
}
return View(); // will render normal Index.cshtml
}
}
Let me know if you have any questions or need clarifications on any part.
UPDATE
I have one issue with the way! You get the ID from db with State_name, then get the model from db by State_name! Why not retrieve
model from db by State_name at the first time?
look:
public class CategoryController : Controller {
// GET: /Category/
// OR
// GET: /Category/State_name
public ActionResult Index(string State_name) {
if (!string.IsNullOrWhiteSpace(State_name)) {
var model = FindStateByName(State_name);
if(model != null)
return View("Cities", model);
else
// means the specified state_name not found! u can do something else, or allow continue bellow lines
}
return View(); // will render normal Index.cshtml
}
}
and if you are on EF, then you can create this method:
public State FindStateByName(state_name){
using(var context = new YourEntityContext()){
return context.States.SingleOrDefault(s => s.Name.ToLower() == state_name.ToLower());
}
}
Why not using this way?
This should do it:
routes.MapRoute("Category_byState", "Category/{State_id}", new { controller = "Category", action = "Cities" });

How to pass model to an action which have other parameters

My problem with the following is how do I send the ModelStateErrors to the action Employee, when I go through the catch part in DeleteEmployee
public ActionResult Employee(int ID, string Name)
{
EmployeeListModel model = new EmployeeListModel (ID, projectName);
return View(model);
}
public ActionResult DeleteEmployee(Employee emp)
{
try
{
emp.Delete();
return RedirectToAction("Employee", new { ID = emp.ID, Name = emp.Name });
}
catch (Exception e)
{
EmployeeListModel model = new EmployeeListModel (emp.ID, emp.Name);
ModelState.AddModelError("Error", e.Message);
return RedirectToAction("Employee", model);
}
}
With return View("Employee", model); I a still not able to send the ID and Name as parameter.
Use TempData to persist the ModelState across multiple controller actions.
MvcContrib has an action filter that does this. Jeremy Skinner wrote the code and a blog post about it at http://www.jeremyskinner.co.uk/2008/10/18/storing-modelstate-in-tempdata-with-aspnet-mvc/. The link there for the source is broken, so I posted the code below.
ModelStateToTempDataAttribute Source Code
/// <summary>
/// When a RedirectToRouteResult is returned from an action, anything in the ViewData.ModelState dictionary will be copied into TempData.
/// When a ViewResultBase is returned from an action, any ModelState entries that were previously copied to TempData will be copied back to the ModelState dictionary.
/// </summary>
public class ModelStateToTempDataAttribute : ActionFilterAttribute
{
public const string TempDataKey = "__MvcContrib_ValidationFailures__";
/// <summary>
/// When a RedirectToRouteResult is returned from an action, anything in the ViewData.ModelState dictionary will be copied into TempData.
/// When a ViewResultBase is returned from an action, any ModelState entries that were previously copied to TempData will be copied back to the ModelState dictionary.
/// </summary>
/// <param name="filterContext"></param>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var modelState = filterContext.Controller.ViewData.ModelState;
var controller = filterContext.Controller;
if(filterContext.Result is ViewResultBase)
{
//If there are failures in tempdata, copy them to the modelstate
CopyTempDataToModelState(controller.ViewData.ModelState, controller.TempData);
return;
}
//If we're redirecting and there are errors, put them in tempdata instead (so they can later be copied back to modelstate)
if((filterContext.Result is RedirectToRouteResult || filterContext.Result is RedirectResult) && !modelState.IsValid)
{
CopyModelStateToTempData(controller.ViewData.ModelState, controller.TempData);
}
}
private void CopyTempDataToModelState(ModelStateDictionary modelState, TempDataDictionary tempData)
{
if(!tempData.ContainsKey(TempDataKey)) return;
var fromTempData = tempData[TempDataKey] as ModelStateDictionary;
if(fromTempData == null) return;
foreach(var pair in fromTempData)
{
if (modelState.ContainsKey(pair.Key))
{
modelState[pair.Key].Value = pair.Value.Value;
foreach(var error in pair.Value.Errors)
{
modelState[pair.Key].Errors.Add(error);
}
}
else
{
modelState.Add(pair.Key, pair.Value);
}
}
}
private static void CopyModelStateToTempData(ModelStateDictionary modelState, TempDataDictionary tempData)
{
tempData[TempDataKey] = modelState;
}
}
Here are some similar posts
ASP.NET MVC - How to Preserve ModelState Errors Across RedirectToAction?
How can I maintain ModelState with RedirectToAction?
From the view, the model state dictionary is a part of the ViewDataDictionary, so try accessing it through viewdata, at least in MVC 3 (believe that way in older versions as well). You don't need to pass it through the model, but instead access it this way.
However, i don't know if model state errors are retained if you do a redirect; you may want to return the response directly instead:
return View("Employee", model);
HTH.
The values for ID and Name are being sent wrapped up in the EmployeeListModel you pass to the "View" ActionResult:
Model.ID
Model.Name
Note, #Brian is correct in that you'll need to use the "View" ActionResult rather than "RedirectToAction" ActionResult otherwise the modelstate errors would be lost. The alternative is to store your modelstate in TempData which is really a special wrapper around the session object. You would need to use something like the "RedirectToAction" ActionResult if you need to ensure your url's are updated properly ...
Store the ModelState in TempData:
TempData["ModelState"] = ModelState;
Then merge the ModelState using an action filter for example:
protected override void OnActionExecuted(ActionExecutedContext context)
{
filterContext.Controller.ViewData.ModelState.Merge((ModelStateDictionary)TempData["ModelState"]);
}
Hope this helps.
Try below code
//as a best practise always use Viewmodel e.g. employeeViewMode and strongly type your view to viewmodel
public class EmployeviewModel
{
public int id;
public string name;
public string errormessage;
}
public ActionResult Employee(int ID)
{
EmployeeListModel model = new EmployeeListModel(ID, projectName);
EmployeviewModel vm = new EmployeviewModel();
vm.id = model.id;
vm.name = model.name;
if (TempData.Keys.Count() > 0)
{
vm.errormessage = TempData["errormessage"];
}
return View(vm);
}
public ActionResult DeleteEmployee(Employee emp)
{
try
{
emp.Delete();
return RedirectToAction("Employee", new { ID = emp.ID, Name = emp.Name });
}
catch (Exception e)
{
//use TempData to store error message
TempData["ErrorMessage"] = e.message;
return RedirectToAction("Employee", emp.ID);
}
}

ASP.NET MVC Actions that return different views, or just make a ton of Actions?

So, I am in a situation, where I need to display a different view based on the "Role" that the authenticated user has.
I'm wondering which approach is best here:
[Authorize(Roles="Admin")]
public ActionResult AdminList(int? divID, int? subDivID)
{
var data = GetListItems(divID.Value, subDivID.Value);
return View(data);
}
[Authorize(Roles = "Consultant")]
public ActionResult ConsultantList(int? divID, int? subDivID)
{
var data = GetListItems(divID.Value, subDivID.Value);
return View(data);
}
or should I do something like this
[Authorize]
public ActionResult List(int? divID, int? subDivID)
{
var data = GetListItems(divID.Value, subDivID.Value);
if(HttpContenxt.User.IsInRole("Admin"))
{ return View("AdminList", data ); }
if(HttpContenxt.User.IsInRole("Consultant"))
{ return View("ConsultantList", data ); }
return View("NotFound");
}
In the case where the action is conceptually the same, but the display is different, I would have one action and return different views depending on your discriminator. I'd go with your second example, slightly modified. Note that there is no need to get the data if the user isn't in an appropriate role.
[Authorize]
public ActionResult List(int? divID, int? subDivID)
{
var view = HttpContext.User.IsInRole("Admin")
? "AdminList"
: (HttpContext.User.IsInRole("Consultant")
? "ConsultantList"
: null);
if (view == null)
{
return View("NotFound");
}
var data = GetListItems(divID.Value, subDivID.Value);
return View( view, data );
}
You realize, of course, that you have the potential for an unhandled exception when you refer to the Value of a potentially null Nullable<int>, correct?
Also, you could, if doing this frequently, refactor the construction of the view prefix into a common method.
public string GetRolePrefix()
{
return HttpContext.User.IsInRole("Admin")
? "Admin"
: (HttpContext.User.IsInRole("Consultant")
? "Consultant"
: null);
}
Then call it as
...
var prefix = GetRolePrefix();
if (prefix == null)
{
return View("NotFound"); // more likely "NotAuthorized" ???
}
...get model...
return View( prefix + "List", data );
I prefer the second method, however I think your controller could be tidied up a bit. There's too much logic in there for my liking, which could potentially grow as more roles are added.
One approach would be to refactor the ugly role checking code into a service layer (which could be injected if you're using an IoC container):
[Authorize]
public ActionResult List(int? divID, int? subDivID)
{
var permission = _userService.GetKeyRole(HttpContext.User);
if(permission != null)
{
var data = GetListItems(divID.Value, subDivID.Value);
return View(permission + "List", data );
}
return View("NotFound");
}
And the extracted method:
public class UserService : IUserService
{
public string GetKeyRole(IPrincipal user)
{
if(user.IsInRole("Admin")) return "Admin";
if(user.IsInRole("Consultant")) return "Consultant";
return null;
}
}
Here is a third approach. It's a combination of both of the approaches suggested by Nate, retains single place for logic like in the second approach, but splits users into actions like in the first one.
[ChildActionOnly]
public ActionResult List(int? divID, int? subDivID)
{
var data = GetListItems(divID.Value, subDivID.Value);
return View(data);
}
[Authorize(Roles="Admin")]
public ActionResult AdminList(int? divID, int? subDivID)
{
return List(divID, subDivID);
}
[Authorize(Roles = "Consultant")]
public ActionResult ConsultantList(int? divID, int? subDivID)
{
return List(divID, subDivID);
}
The trick here is that MVC will try to search for views named by the request action rather than the one that produces output. So when you run AdminList it will actually return List action with AdminList view.
Edit: Was the downvote for not answering the question properly? Let me rephrase my answer.

Resources