How can i make data available in my MVC _Layout view - c#

My _Layout uses the BaseViewModel to render the user's name in the navbar which i want to keep consistent through the application. My HomeController has an action on it called Login that passes a UserViewModel to the Dashboard view upon successful login. UserViewModel derives form BaseViewModel and is only used in the Dashboard view right now.
My question is how do I make this BaseViewModel which will be used by the _Layout page be available throughout the views of the application. Do I have to keep making a call to my service (Database) to fetch this data each time a page loads? because the data that the BaseViewModel needs is only fetched in the Login action of the HomeController so the page breaks if i navigate to another view, and I get this error below
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[InventoryManager.Web.Models.ProductViewModel]', but this dictionary requires a model item of type 'InventoryManager.Web.Models.BaseViewModel'.
BaseViewModel.cs
public class BaseViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
UserViewModel
public class UserViewModel : BaseViewModel
{
public Guid UserId { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
BaseController
public class BaseController : Controller
{
//
// GET: /Base/
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var model = filterContext.Controller.ViewData.Model as BaseViewModel;
}
}
HomeController
public ActionResult Login(LoginViewModel model)
{
if (!ModelState.IsValid)
{
return ReturnLoginViewOnError(CustomErrorMessages.LOGIN_CREDENTIALS_NOT_PROVIDED);
}
var userService = new UserServiceClient();
var user = userService.GetUser(model.Username, model.Password);
if (null == user)
{
return ReturnLoginViewOnError(CustomErrorMessages.INVALID_USER);
}
var userViewModel = Mapper.Map<UserContract, UserViewModel>(user);
return RedirectToAction("Index", "Dashboard", userViewModel);
}

You can keep your UserViewModel and BaseViewModel and use composition to send a compatible type to your View and avoid the error as show below. This approach uses what is referred to as Object Composition
See below, Create AppViewModel class
public class AppViewModel
{
public UserViewModel UserViewModel { get; set; }
public List<ProductViewModel> ProductViewModel { get; set; }
}
// Login Action Method or any action method
Populate AppViewModel to send to the view
public class HomeController {
//Action method
public ActionResult Login(LoginViewModel model)
{
//Do stuff and populate AppViewModel
UserViewModel userViewModel = new UserViewModel {Username = "username", Password ="secret", FirstName = "John", LastName = "Doe"};
AppViewModelmodel model = new AppViewModel{UserViewModel = userViewModel };
return RedirectToAction("Index", "Dashboard", model);
}
}
// ProductController
public class ProductController
{
public ActionResult Products()
{
ProductViewModel productViewModel = new ProductViewModel { /*Initialize properties here*/};
AppViewModel model = AppViewModel { ProductViewModel = new List<ProductViewModel>{ new ProductViewModel = productViewModel }};
return View(model);
}
}
// Dashboard View
// Do include your model namespace
#model AppViewModel
<div>
<p> FirstName : #Model.UserViewModel.FirstName</p>
</div>
// Products View
// Do include your model namespace
#model AppViewModel
<div>
//You get the idea
<p> Product Name: #Model.ProductViewModel.Select( x => x.ProductName). </p>
</div>

What I usally do, is that I create an LayoutController. With this controller, I render all the persistent information which is used on the layout pages.
public class LayoutController : Controller
{
private readonly IProvideData _provider;
public LayoutController(IProvideData provider)
{
_provider = provider;
}
[ChildActionOnly]
public ActionResult AccountInformation()
{
var model = _provider.GetUserStuff();
return PartialView("_AccountInformation", model);
}
}
The ChildActionOnly attribute ensures that an action method can be called only as a child method from within a view. On my _Layout.cshtml I can render this action with:
#{ Html.RenderAction("AccountInformation", "Layout"); }
Which renders the _AccountInformation partial view which can look like:
#model MyApplication.ViewModels.UserInformation
Username: #Model.UserName

Related

Form submission in partial views in MVC

I am developing a simple mvc application . The code is as follows:
Model .cs:
public class CustomModel
{
public IEnumerable<lang> lstlang { get; set; }
public IEnumerable<org> lstOrg { get; set; }
}
public class lang
{
public int langid { get; set; }
public string langName { get; set; }
}
public class org
{
public int orgId { get ;set;}
public string orgName { get; set; }
}
Controller.cs
public Action Index()
{
// Get data from database and fill the model
var model = new CustomModel();
return View(model);
}
public Action Partial()
{
// Get data from database and fill the model
var model = new CustomModel();
return PartialView(model);
}
[HttpPost]
public Action Partial(FormCollection frm, CustomModel model)
{
// Get data from database and fill the model
var model = new CustomModel();
return PartialView(model);
}
Index.cshtml
#model CustomModel
#Html.TextboxFor(x => x.lang.FirstOrDefault().id);
<input type="button" id="btn" />
#Html.RenderPartial("Partial", model)
Partial.cshtml
#model CustomModel
#Html.TextboxFor(x => x.lang.FirstOrDefault().id);
<input type="submit" id="submit" />
The thing is, when I click the submit button in the Partial.cshtml page, and examine the model in httppost method in public Action Partial(FormCollection frm, CustomModel model), the model contains null for both lists lstlang and lstOrg, but the formcollection[0] will give the selected textbox value.
What am I missing, or is this the right way of using partial views?
Don't use FirstOrDefault(). If you want to post something back to the front end with collections, you'll need to use indexing.
Public class CustomModel
{
public ICollection<lang> lstlang { get; set; }
public ICollection<org> lstOrg { get; set; }
}
#HTML.textboxfor(x=>x.lang[0].id);

ASP.NET MVC Passing action parameter to the view

Let's say that I have a Action method like this:
public ActionResult Page(int? id)
{
}
The question is, how can I read 'id' parameter in View?
Your code won't build successfully, until you return a view like this
public ActionResult Page(int? id)
{
return View();
}
and because you want to return id to your view you can do
Simple object
public ActionResult Page(int? id)
{
return View(id);
}
Just remember to accept the new value in your view, by dong the following at the top (very first line)
#model int?
ViewModel approach
public class MyViewModel
{
public int? Id { get; set; }
//Other properties here
}
public ActionResult Page(int? id)
{
var myViewModel = new MyViewModel()
{
Id = id
};
return View(myViewModel);
}
and then in your view
#model MyViewModel
You can access parameters from Request in view as below
Request.Params["id"]

Pass a viewModel to the layout

I have this viewmodel that has some properties and stuff that i would like to apply
to the layoutpage:
public class BasicViewModel
{
public Page Page { get; set; }
public List<Settings> Settings { get; set; }
}
From other threads here have i understood that this is possible but i dont really understand how.
From what I understand I somehow need to modify a controller and this is where I get confused. How do I know what controller that has to be modified and how?
Any help appreciated.
In controller, Prepare an action like
public ActionResult BasicViewModelDemo
{
BasicViewModel obj=new BasicViewModel()
// assign properties
return View(obj);
}
and view write some jquery. (Here i am using knockout to make view model)
<script>
var model='#Html.Raw(Json.Encode(Model))';
var viewmodel = ko.mapping.fromJSON(model);
</script>
Here goes my solution -
Lets say you have a model of this way -
public class BasicViewModel
{
public Page Page { get; set; }
public List<Settings> Settings { get; set; }
}
public class Page
{
public string PageName { get; set; }
}
public class Settings
{
public string SettingName { get; set; }
}
Then in the controller you should initiate the model in this way -
public class HomeController : Controller
{
BasicViewModel model;
public HomeController()
{
model = new BasicViewModel();
model.Page = new Page();
model.Settings = new List<Settings>();
}
public ActionResult Index()
{
model.Page.PageName = "My Page";
ViewBag.LayoutModel = model;
return View();
}
}
So basically we used Constructor to initiate the model and then we assign proper values in the controller action.
Then in the Layout, we can use the Model property as shown below -
<div> #ViewBag.LayoutModel.Page.PageName </div>

Remote validation doesn't pass data to action

I have model:
public class MyModel
...fields
[Remote(ActionName, ControllerName)]
public string SomeNumber { get; set; }
..fields
And have a action in ControllerName controller:
public JsonResult ActionName(string someNumber)
{...}
But when actions is invoked the parameter someNumber is allways null.
And when I try to debug it I get
GET /ControllerName/ActionName?MyModel.SomeNumber =34189736
How can I make it work?
(I can't pass whole model MyModel, and cant change MyModel.SomeNumber name of field in my view)
UPD. Input in my view:
<input data-val="true" data-val-remote-additionalfields="*.SomeNumber" data-val-remote-url="/ControllerName/ActionName" id="MyModel_SomeNumber" name="MyModel.SomeNumber" type="text" value="34189734" class="valid">
UPD solved! :)
I create new model with single field SomeNumber and use prefix:
SomeNumber([Bind(Prefix = "MyModel")]MySingleFieldModel model)
If you're using nested ViewModels, you'll need to accept the parent ViewModel as the argument in your Validation action. For example:
public class ParentViewModel
{
public UserViewModel User {get; set; }
//....
}
public class UserViewModel
{
[Remote("UniqueUsername", "Validation")]
public string Username { get; set; }
//....
}
In ValidationController:
public class ValidationController : Controller
{
public JsonResult UniqueUsername(ParentViewModel Registration)
{
var Username = Registration.User.Username; //access the child view model property like so
//Validate and return JsonResult
}
}
Try using you model as the parameter.
So that it could bind the value to it.
public JsonResult ActionName(MyModel model)
{
//...
model.SomeNumber;
//...
return Json(validationResult, JsonRequestBehavior.AllowGet)
}
public JsonResult ActionName(string SomeNumber)
{...}
I think you may need to match case on your input parameter.

How to pass generic parameter to partial view [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Generic partial view: how to set a generic class as model?
I am trying to build common functionality using generic types but got stuck with below scenario.
View Model
public class DeleteForm<T>
{
public LogInfo Ticket { get; set; }
public string Id { get; set; }
public DeleteForm() {
Ticket = new LogInfo();
}
public DeleteForm(T viewModel) : this() {
ViewModel = viewModel;
}
public T ViewModel { get; set; }
}
Controller
public ActionResult Index(string name)
{
return View("index", new DeleteForm<List<Users>>(new List<Users>());
}
List Screen
#model DeleteForm<List<Users>>
//gridview displays list of users
#Html.Partial("revisionwindow", Model)
Partial View
#model DeleteForm<T> <---Its not working
#Html.EditorFor(o=>o.Ticket)
#Html.EditorFor(o=>o.Id)
use dynamic model instead.
your partial view can look something like this :
#model dynamic
#{
var myModel = (DeleteForm<List<Users>>) Model;
}
#Html.EditorFor(o=>myModel.Ticket)
#Html.EditorFor(o=>myModel.Id)
hope this help.
If you pass a model to view, it has to be strongly-typed (particular type).
So SomeClass<T> type won't work. Instead of generic type a base class could fill
your requirements. What I mean is:
View Model
public abstract class Form
{
public Form()
{
Ticket = new LogInfo();
}
public LogInfo Ticket {get; set;}
public int Id {get; set;}
}
public class DeleteUsersForm: Form
{
public DeleteUsersForm(IEnumerable<Users> users):base()
{
this.ViewModel = users;
}
public IEnumerable<Users> ViewModel {get; set;}
}
Controller
public ActionResult Index(string name)
{
return View(new DeleteUsersForm(new List<Users>()));
}
List Screen
#model DeleteUsersForm
//displays list
#Html.Partial("revisionwindow", Model)
Partial View
#model Form
#Html.EditorFor(o=>o.Ticket)
#Html.EditorFor(o=>o.Id)

Categories