How to combine viewmodels? - c#

I have two Viewmodels, a view and two partial views and a controller method for submit.
Lets name them, BigViewModel, SmallViewModel, Page.cshtml, _Options.cshtml, _Submit.cshtml and Submit(BigViewModel) method in controller.
SmallViewModel is included in BigViewModel as one of the properties.
like,
public class BigViewModel
{
public bool Confirm { get; set; }
public SmallViewModel s { get; set; }
}
Now, Page.cshtml has a division where _Options.cshtml resides where user can input few options like sorting, searching etc. This _Options.cshtml uses a viewmodel called SmallViewModel. When you click on Submit button which is on _Options.cshtml partial view, it opens a dialog box that is _Submit.cshtml which asks for confirmation and submit button. This confirmation information is stored in BigViewModel. Now, when I hot submit it goes to Submit(BigViewModel) method in the controller.
My problem is how to include SmallViewModel data into BigViewModel from _Submit.cshtml partial view page. Because, when I go to the Submit(BigViewModel) method, I only see Confirm - True/False. But smallViewModel is null.
Hope, question is clear enough. I didn't know how else to ask. Please help.

You can simply create an interface that all models with options will derive from. Here is a sample :
public class BigViewModel : IModelOptions
{
public bool Confirm { get; set; }
public SmallViewModel SmallView { get; set; }
}
public class SmallViewModel
{
public string Stuff{ get; set; }
}
public interface IModelOptions
{
SmallViewModel SmallView { get; set; }
}
this way if you ever need more models with options you can simple do "NewModel : IModelOptions". Now your _options view would look something like
#model MvcApplication1.Models.IModelOptions
#Html.TextAreaFor(x => x.SmallView.Stuff)
Note that now your partial view does not need to be aware of what model is being passed in. So long as it derives from IModelOptions then it knows it will have a SmallView. Our _submit would be something like
Are you sure? If so I will use fancy javascript to allow you to submit!
<input type="submit"/>
and then we tie that all together in our main view (Index , Page, what have you)
#model MvcApplication1.Models.BigViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.Confirm)
{
Html.RenderPartial("_Options");
Html.RenderPartial("_Submit");
}
}
Note that here we take in a BigViewModel. That should work fine as our BigViewModel houses Options and derives from IModelOptions.
Our controller would simply be
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(BigViewModel model)
{
var smallVIewModelInfo = model.SmallView.Stuff;
var bigViewModelConfirm = model.Confirm;
return View();
}
}
and now you should be getting data from both small and big view model.

Related

How to use asynchronous ViewModel in every controller action?

I'm new to asp.net mvc (5) and I am facing an issue on my website.
Basically, all aspnet_users are linked to my specific user table via the guid.
I have a BaseController class with a UnitOfWork and a ViewModelBase :
public class BaseController : Controller
{
protected UnitOfWork UnitOfWork { get; private set; }
public ViewModelBase ViewModel { get; set; }
}
(Extract of) the ViewModelBase class, containing the information needed in the layout page :
public class ViewModelBase
{
public User User { get; set; }
public bool IsAuthentified { get; set; }
public bool Loaded { get; set; }
}
And the layout which uses it :
#model Project.ViewModels.ViewModelBase
<html>
<body>
Some very cool layout stuff related to the User in the ViewModelBase
</body>
</html>
Here is an example of use with the HomeController :
public class HomeController : BaseController
{
private new HomeViewModel ViewModel
{
get { return (HomeViewModel)base.ViewModel; }
}
public HomeController()
{
ViewModel = new HomeViewModel();
}
public ActionResult Index()
{
// I need to get the MembershipUser here to retrieve the related User and set it in the ViewModel
return View(ViewModel);
}
}
The HomeViewModel :
public class HomeViewModel : ViewModelBase
{
public string HomeSpecificProperty { get; set; }
}
And the view :
#model Project.ViewModels.HomeViewModel
#{
ViewBag.Title = "Home";
Layout = "~/Views/Shared/Layout.cshtml";
}
Welcome #Model.User.UserName !
<br />
#Model.HomeSpecificProperty
And here is my problem. The thing is all the pages have a viewmodel inherited from ViewModelBase, since it is used in the layout, but I don't know where nor how to set the User property in it.
Since Membership is used to retrieve the User it has to be in an Action, I can't do this in the ViewModelBase constructor.
Thus I added this code in the BaseController, which sets the ViewModelBase properties on the first get :
private ViewModelBase viewModel;
protected ViewModelBase ViewModel
{
get
{
if (!viewModel.Loaded)
LoadBaseContext();
return viewModel;
}
set { viewModel = value; }
}
private void LoadBaseContext()
{
viewModel.IsAuthentified = HttpContext.User.Identity.IsAuthenticated;
if (viewModel.IsAuthentified)
{
MembershipUser user = Membership.GetUser();
viewModel.Player = UnitOfWork.UserRepo.Get((Guid)user.ProviderUserKey);
}
viewModel.Loaded = true;
}
Might not be very beautiful, but works. However, since there is some database acesses in it (notably to get the User), I thought I should put the LoadBaseContext function async.
I tried, and since all actions use ViewModelBase I put every action async too. But then #Html.ActionLink doesn't work anymore since the action called is async.
Finally, my question is : Is this ViewModelBase and User property the right way to do ? If it is, should the LoadBaseContext be async ? Then, how to make it work with the actions ?
Thanks a lot.
UPDATE
Extract of the layout :
#if (!Model.IsAuthentified)
{
#Html.Action("Index", "Authentification", null) // display login partial view
}
else
{
#Html.Action("Index", "UserInfos", null) // display userinfos partial view
}
Authentification controller Index's action :
public ActionResult Index()
{
ViewModel = new AuthentificationViewModel();
// loads the ViewModelBase properties here
return PartialView("~/Views/Shared/Partials/Login.cshtml", ViewModel);
}
If you have something that you always want in all of your views, you could either ensure that every view takes a view model that includes or extends that core set of data, or you could simply put the data in the ViewBag.
To do the latter, you could override OnActionExecuting in your base controller, and set the ViewBag contents there.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
this.ViewBag["IsAuthenticated"] = this.Request.IsAuthenticated;
// ...
}
If you want async behaviour, then you might be able to adapt this post to your needs.
To do this without overriding OnActionExecuting, you could put an async method in your controller base class that sets the data in the ViewBag:
protected async virtual Task PopulateViewBag()
{
this.ViewBag["foo"] = await MyAsyncMethod();
// ...
}
... and then simply call this from each and every one of your controller actions:
public async Task<ActionResult> MyAction()
{
await this.PopulateViewBag();
// ... more code
}
It would be a bit tedious, though.
One final word: depending on how many users you expect to have etc. it may be easier to get the user information once, and then cache it (e.g. in the Session), rather than repeatedly get it during every request. Presumably most of the user info isn't going to change between requests...

ViewModel vs. InputModel in Razor View

As I found on 10 Good practices for asp.net mvc webapplications it is a good practice to split mvc models in ViewModel (Models representing the view) and InputModels (Representing data entered by user).
The ViewModel gets a property of Type InputModel. The InputModel carries the data that can be edited by the user.
public class EmployeeInputModel{
public string Name {get;set;}
public Id? DepartmentId{get;set;}
}
public class EmployeeCreateViewModel{
public IList<Department> Departments{get;set;}
public EmployeeInputModel EmployeeModel{ get;set;}
}
The Controller-methods look like:
public class EmployeeController : Controller{
public ActionResult Create(){
var viewModel = new EmployeeCreateViewModel(DepartmentService.GetAll(), new EmployeeInputModel());
return View(viewModel);
}
public ActionResult Create(EmployeeInputModel createModel){
try{
EmployeeService.Create(...);
return RedirectToAction("Index");
} catch (Exception ex){
var viewModel = new EmployeeCreateViewModel(DepartmentService.GetAll(), createModel);
return View(viewModel)
}
}
}
The View looks like:
#model EmployeeCreateViewModel
#Html.EditorFor(m => m.EmployeeModel)
The Editor Partial is just like:
#model EmployeeInputModel
#Html.TextBoxFor(m => m.Name)
#Html.DropDownFor(m => m.Department, ???)
This worked great for me, unless I came to the point of DropDownLists (in the sample see departments). Because the EditorTemplate doesnt know the ViewModel but just the InputModel.
I dont want to put the department list into the input model, because it is not the supposed place to be for this list (I would have to bind them). It has to be in the viewmodel. The properties of the input model should also not be in the ViewModel.
Does someone have any idea how to achieve a separation of viewmodel and input model in one view?
You would have to create a model abstraction of your departments. Something like this:
public class EmployeeInputModel{
public string Name {get;set;}
public List<DepartmentInputModel> Departments{get;set;}
}
public class DepartmentInputModel{
public int Id;
public string Name;
}
Then you can display just the name of the department in the drop down list. The value will then be the id of the department.
You can have a look at this SO question for a example.
You can use the ViewBag yo pass the list of departments to your partial.
Or use a partial view that accepts the view model as its model

ViewModel loses data on post

I'm trying to create a complex ViewModel that has both an Index Page and Create Page of Company Notes all within the Details Page of a Company, and would like some guidance as to whether I'm doing this properly.
My problem at the moment is that when I create a new Company Note, it doesn't have any information in the object beyond the EditorFor fields I include in my cshtml - it loses all the data in the ViewModel.
I have a Company model and CompanyController, and in my Details action, I populate all the notes that are relevant to the company, and a form to allow users to add a new note.
My Company and CompanyNotes model are very simple:
public class Company
{
public int CompanyID { get; set; }
// bunch of fields related to the company
public virtual ICollection<CompanyNote> CompanyNotes { get; set; }
}
public class CompanyNote
{
public int CompanyNoteID { get; set; }
public DateTime Date { get; set; }
public string Note { get; set; }
public Company Company { get; set; }
}
I have a ViewModel that looks like this:
public class CompanyViewModel
{
public Company Company { get; set; }
// List of all notes associated with this company
public IEnumerable<CompanyNote> CompanyNotes { get; set; }
// A CompanyNote object to allow me to create a new note:
public CompanyNote CompanyNote { get; set; }
}
This is my Details action, which populates the company record, gets a list of related notes, and displays a create form with a new, empty object:
public ActionResult Details(int id = 0)
{
var viewModel = new CompanyViewModel();
viewModel.Company = db.Companies.Find(id);
if (viewModel.Company == null)
{
return HttpNotFound();
}
viewModel.CompanyNotes = (from a in db.CompanyNotes
where a.Company.CompanyID.Equals(id)
select a).OrderBy(x => x.Date);
viewModel.CompanyNote = new CompanyNote
{
Date = System.DateTime.Now,
Company = viewModel.Company
};
return View(viewModel);
}
This is my CreateNote action in my CompanyController. (Should I split this out into a separate partial view? What would be the benefit?)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateNote(CompanyViewModel companyViewModel)
{
CompanyNote companyNote = companyViewModel.CompanyNote;
if (ModelState.IsValid)
{
db.CompanyNotes.Add(companyNote);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(companyViewModel);
}
Finally, here's a simplified version of detail.cshtml:
#model Project.ViewModels.CompanyViewModel
// My company detail display is here, removed for sake of berevity
#using (Html.BeginForm("CreateNote", "Company"))
{
#Html.EditorFor(model => model.CompanyNote.Date)
#Html.TextAreaFor(model => model.CompanyNote.Note})
<input type="submit" value="Create" />
}
When I post, my CreateNote action has a companyViewModel that is basically empty, with the exception of companyViewModel.CompanyNote.Date and companyViewModel.CompanyNote.Note, which are the fields in my form - all the other data in the ViewModel is null, so I'm not sure how to even include a reference back to the parent company.
Am I even on the right path here?
Thanks,
Robbie
When I post, my CreateNote action has a companyViewModel that is
basically empty, with the exception of
companyViewModel.CompanyNote.Date and
companyViewModel.CompanyNote.Note, which are the fields in my form -
all the other data in the ViewModel is null, so I'm not sure how to
even include a reference back to the parent company.
That's perfectly normal behavior. Only information that is included in your form as input fields is sent to the server when you submit the form and this is the only information you could ever hope the model binder be able to retrieve.
If you need the CompanyNotes collection in your HttpPost action simply query your backend, the same way you did in your GET action. You could do this by passing the company ID as a hidden field:
#Html.HiddenFor(model => model.Company.CompanyID)
So the idea is to only include as input fields in your form information that the user is supposed to somehow modify. For all the other information, well, you've already have it in your backend so all you have to do is hit it to get it.
Contrary to classic WebForms, there's no longer any notion of ViewState in ASP.NET MVC. It is much closer to the stateless nature of the HTTP protocol.

Do you always have to set ViewBag variables in order to access data from the controller

I have a controller like this:
public ActionResult Index()
{
ViewBag.Title = "Index";
ViewBag.LoggedIn = TheUser.CheckStatus();
return View();
}
Thing is, I have to set LoggedIn to the output of my other function TheUser.CheckStatus() so that I can reference it with razor... Is there a way in Razor to access a function straight off? for example...
#TheUser.CheckStatus
instead of
#ViewBag.LoggedIn
The recommended way in MVC for passing information to a view is to create a model specific to that view (aka view model) e.g.
public class IndexViewModel
{
public string Title { get; set; }
public bool IsAuthenticated { get; set; }
}
....
public ActionResult Index()
{
return View(new IndexViewModel()
{
Title = "Index",
IsAuthenticated = UserIsLoggedIn()
});
}
However, to answer your question:
Is there a way in Razor to access a function straight off?
If you are using ASP.NET Membership you can use the IsAuthenticated property on the request e.g.
#Request.IsAuthenticated
Otherwise, you do need to pass this information to the view (whether that be via ViewBag/view model etc.)
Alternatively, you could write your own extension method for Request which would allow you to access it directly in the view:
#Request.UserLoggedIn()
Or even as a HtmlHelper e.g.
public static class HtmlHelperExtensions
{
public static bool UserIsLoggedIn(this HtmlHelper helper)
{
return /* authentication code here */
}
}
Then in your views you can use #Html.UserIsLoggedIn() which I think is what you are after.
use a ViewModel class (your view will then be strongly typed, and you'll be able to use "classic" helpers).
//viewModel class
public class UserStatusViewModel {
public string Title {get;set;}
public bool IsLogged {get;set;
}
//action
public ActionResult Index() {
var model = new UserStatusViewModel{ Title = "Index", IsLogged = TheUser.CheckStatus()};
return View(model);
}
//view
#model UserStatusViewModel
#Html.DisplayFor(m => m.Title)
#Html.DisplayFor(m => m.IsLoggedIn)

Asp.Net MVC layout and partial views

let's consider two views that use the same layout composed of:
A left column containing a "body" (which is filled differently by both views)
A right column that displays general information (passed via the model)
Instead of defining the right part twice, I wondered if I could create a PartialView to link directly from the layout page.
The problem is that the partial views implicitely inherit their models from the view that is being rendered. And since each view has its own model, I end up with a model type mismatch in the partial view.
From here I see two solutions:
I could insert the common part of the view model in the ViewBag. Unfortunately this means that each view that uses this layout has to implement this "convention" but nothing warns the developer about it at compile time...
I could use polymorphism to make each view model inherit from the same base class (edit: or interface) that the Partial Views uses. This would work up to a certain extend but would potentially exponentially increase in complexity as soon as I have a second partial view in the same layout.
So here are the questions:
Am I right with the assumptions above?
Do you see any other possibility?
Any return on experience on this?
Thanks a lot,
TB.
Use an Interface and implement it on the two models, this is exactly the kind of thing they're used for.
Here is an example of two different Views using two different Models that both implement an interface. This is subtyping instead of ad-hoc polymorphism.
public class ViewModelOne : IReusableView
{
public string Name { get; set; }
public string Something { get; set; }
public int ANumber { get; set; }
}
public class ViewModelTwo : IReusableView
{
public string Name { get; set; }
public string Thing { get; set; }
public string SomethingElse { get; set; }
public int ANumber2 { get; set; }
}
public interface IReusableView
{
string Name { get; }
}
So we have the really simple partial view here that is 'InnerPartialView':
#model TestIntegration.Models.IReusableView
<div>
#Model.Name
</div>
Which is used in the home and about pages of this example controller:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View(new ViewModelOne() { Name = "hello", Something="sdfsdfs", ANumber = 1 });
}
public ActionResult About()
{
return View(new ViewModelTwo() { Name = "hello 2", SomethingElse = "aaaddd", ANumber2 = 10, Thing="rand" });
}
}
The home view:
#model TestIntegration.Models.ViewModelOne
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit http://asp.net/mvc.
#Html.Partial("InnerPartialView")
</p>
The about view:
#model TestIntegration.Models.ViewModelTwo
#{
ViewBag.Title = "About Us";
}
<h2>About</h2>
<p>
Put content here.
#Html.Partial("InnerPartialView")
</p>
When you render the partial view, you can send it a model:
#Html.RenderPartial(MVC.Partials.Views.Sidebar, Model.SideBarModel);
So you could send down data as part of the parent model that is the model for the partial sidebar.
In partial views, models are of type dynamic so you don't need to know what type they are. However, you just need to make sure the model has the property you need. In other words you can use Model.MyProperty or Model.MyProperty as MyPropertyType when using Html.Partial.

Categories