passing global data in partial views without duplicating code - c#

I have an application where I need to display a username and image for the current user. I could pull the data into a viewmodel and display in the view, however this would mean that i would need to do this in every controller that uses the view. Is there a way where I can global set values and call in some partial views without having to repeatedly duplicate it in each controller?

Sounds to me like you need to call a child action from your _Layout.cshtml. Here's how it could look.
First from what you have said I am assuming your viewModel will be this
public class UserDisplayViewModel
{
public string UserName { get; set; }
public string ImageUrl { get; set; }
}
You will need a controller that is responsible for getting the username and image data.
It would look like this
public class UserController : Controller
{
[ChildActionOnly]
public ActionResult _userDisplay()
{
var viewModel = GetUserNameAndImage();
return View(viewModel);
}
private UserDisplayViewModel GetUserNameAndImage()
{
//code for getting the username and image data
}
}
You would call the child action from your _layout.cshtml file like this
#Html.Action("_userDisplay", "User")
It goes wherever you want the username and image to appear in your HTML.
You will also need a partial view called _userDisplay.cshtml which will contain the markup for how you want to display your username and image. Here's a very basic example
#model UserDisplayViewModel
#{
Layout = null;
}
<p>
Username: #Model.UserName
</p>
<p>
<img src="#Model.ImageUrl"/>
</p>

Related

MVC structure query

Evening all, I'm chasing my tail trying to get the structure of a simple ASP.NET MVC correct. From the start I should say I'm completely new to ASP and MVC, I've used a bit C# before.
For examples sake let's say I'm trying to check two pieces of information e.g. a username and password that a user enters, against a database that stores all the user info. If they put in the correct credentials a summary of the user info is displayed, if not they are taken back (well visually never leave) the login page with a please try again message.
So I have my Home (Login page) view, a HomeController and a HomeIndexViewModel. Similarly I have an Account view, a AccountController and an AccountUserViewModel (there is also AccountIndexViewModel but that isn't really used).
The Home view 'takes in' (through it's controller) a HomeIndexVM as it's model:
#model ViewModels.HomeIndexViewModel
#using (Html.BeginForm("User", "Account", FormMethod.Post))
{
if (#Model.PreviousAttempts)
{
<p><b>Username or password were not recognised please try again.</b></p>
}
<p>Username: #Html.TextBoxFor(x => x.Username)</p>
<p>Password: #Html.PasswordFor(x => x.Password)</p>
<input id="btnLogin" type="submit" value="Login" />
}
HomeController:
public class HomeController : Controller
{
public ActionResult Index(bool invalidLogin = false)
{
var vm = new HomeIndexViewModel() { Username = string.Empty, Password = string.Empty, PreviousAttempt = invalidLogin };
return View(vm);
}
}
And finally the HomeIndexViewModel.cs
public class HomeIndexViewModel
{
public string Username { get; set; }
public string Password { get; set; }
public bool PreviousAttempt { get; set; }
}
I think that is okay so far. Now on clicking the login button, it will post to Account, User.
public ActionResult User(UserLogin userLogin)
{
if (!ModelState.IsValid)
return RedirectToAction("Index", "Home", new { invalidLogin = true });
// Match username and password against database
User user = userLogin.IsValid(userLogin.Username, userLogin.Password);
if (user != null)
{
return this.View(user);
}
return RedirectToAction("Index", "Home", new { invalidLogin = true });
}
There is a couple of things here, you can see the redirects back to the login page with the true flag to show the failed login message.
But more to my point, clearly it won't work as it takes a UserLogin as a parameter. This is a model object that contains:
public class UserLogin
{
private NewDBSolution_v1Entities accountsDB = new NewDBSolution_v1Entities();
[Required, MinLength(2)]
public string Username { get; set; }
[DataType(DataType.Password), Required]
public string Password { get; set; }
public bool PreviousAttempts { get; set; }
public User IsValid(string username, string password) // Ideally use the local username and password properties rather than pass in as they are the same.
{
// Match username and password against database and return full user info if match found, otherwise return null
}
}
So what am I asking... well is it best practice for, in this case, the Account's User Action to take the HomeIndexViewModel, even although it's Home as opposed to Account related? Or should I pass in a model object as I had originally done, and use it to the validation (what I don't like about this is the validation is done in object that is passed from a different view if that makes sense?)
How do you best bundle information up from a view to pass to an Action? I realize VMs and Model objects the compiler doesn't care they are just classes but I'd like to get my separation of concerns correct.
Basically here all I need is the username and password from the Home view, should that be bundled in to a VM or a M?
It just seems to me there is potential for an awful lot of classes that are just slightly different, so why not make one and use it. I suppose that is where inheritance comes in, but do you gain much there if ever subclass just adds one different property?
Anyway I keep going in circles about the best way to structure. I did read somewhere that VMs should basically be a mask/adapter over the model such that the view only sees just what it needs. But these VMs don't have a model associated with them.
I'm rambling now, if anyone can make head or tail of this and give me a few pointers I'd be very grateful, thanks.
I think you might be going round in circles because you've created the login view in the HomeController. Login code is Account-related, so why not put this into the AccountController instead?
I tend to favour posting to the same action as you get from, if you follow the Post-Redirect-Get pattern, as you're trying to do here. I would move the login action into the AccountController. I would move the code that does the database check into a separate class, rather than keeping it in the model. Perhaps a membership provider or something like that. I would pass that provider into the Controller - this allows the Controller and the Model object to stop worrying about how to decide whether a user is valid or not. They can just ask the provider to tell them.
It's generally good practice to keep your entities (whether UI or domain) simple. They certainly should have database connection objects inside them.
Your Controller would look something like this:
public class AccountController : Controller
{
private readonly IMembershipProvider membershipProvider;
public AccountController(IMembershipProvider membershipProvider)
{
this.membershipProvider= membershipProvider;
}
public ActionResult Login()
{
var viewModel = new LoginViewModel();
return View(viewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
var user = membershipProvider.Find(viewModel.Username, viewModel.Password);
if (user != null)
{
membershipProvider.SignIn(user, true);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("Login", "Your credentials were not recognised. Please try again.");
return View(viewModel);
}
}
Because the Login action has the same name for both Get and Post, your view becomes simpler. You'd have a Login.cshtml view in the Views/Account folder like this:
#model ViewModels.LoginViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken() // good practice to add this, ties in with the ValidateAntiForgeryToken attribute on the Post action
#Html.ValidationSummary() // displays model errors if there are any
<p>Username: #Html.TextBoxFor(x => x.Username)</p>
<p>Password: #Html.PasswordFor(x => x.Password)</p>
<input id="btnLogin" type="submit" value="Login" />
}
To answer this specific question:
How do you best bundle information up from a view to pass to an Action?
You let the default model binder take care of that if you can. It's very powerful and can handle most situations in regards to data being passed back from a form. All the model binder does is match name-value pairs from the form collection that is passed back when the form is posted to properties on your class. Provided your class has the string properties Username and Password the model binder will populate them. The actual class that you provide is irrelevant as far as the model binder is concerned. This would work equally well (but anyone else working on the project wouldn't thank you for it!):
public class Dave
{
[Required, MinLength(2)]
public string Username { get; set; }
[DataType(DataType.Password), Required]
public string Password { get; set; }
}
And then in the Post action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(Dave dave)
{
...
}
Does that help?

Pass a specific instance of a model between views and the controller multiple times without using a form POST

I'm working with ASP.NET MVC5, but very new to it.
Is there a way to pass a specific instance of a model between views and the controller multiple times without using a form POST... for example...
When the controller is handling the request for the "FirstView" page request, the CarModel class is instantiated and an API call is made on the object to retrieve values from an API and set the object's properties.
The object is then passed to the "FirstView" page as the model and the properties are displayed for the user to view.
If the user is happy with the properties, they can then click the ActionLink to save the properties to a database.
I want to pass the entire model object back to the controller so that the work can be done to do the database insert... which I have shown in my controller as "SecondView".
If I am totally off track in terms of how this should be done, please could someone point me in the right direction and link me some reading materials... THANK YOU!
Here is my model...
public class CarModel
{
public string Make { get; set; }
public string Colour { get; set; }
public void GetModelDataFromAPI()
{
Make = "BMW";
Colour = "Black";
// many other properties, but ommitted for brevity
}
public int InsertPropertiesIntoDatabase()
{
// code to do the database insert anbd return an ID
return 1;
}
}
Here is my FirstView.cshtml...
#model CarsApp.Models.CarModel
<html>
<body>
<h1>First View</h1>
Make: #Model.Make, Colour: #Model.Colour
#Html.ActionLink("Add model properties to database", "SecondView", "Home")
</body>
</html>
Here is my controller...
namespace CarsApp.Controllers
{
public class HomeController : Controller
{
public ActionResult FirstView()
{
CarsApp.Models.CarModel objCarModel = new Models.CarModel();
objCarModel.GetModelDataFromAPI();
return View("FirstView", objCarModel);
}
public ActionResult SecondView()
{
// need to get a reference to the model from the first view
// then call the insert method of that model, with the properties
// holding the values from the API
return Content("ID:" + objCarModel.InsertPropertiesIntoDatabase());
}
}
}

Call Html.RenderAction from inside HTML string

I have a ASP.NET MVC 4 application that uses database to store pages (HTML), galleries, forms etc... I want to provide users a functionality to call other controller from inside pages.
Simplified problem is how to call render action from database acquired string. Just for example I would like that string contains #Html.RenderAction("Show", "Gallery", new {id=5})
Another option I have to parse string inside a controller and render all sub calls to string before rendering this HTML.
EDIT:
The database would return something like code bellow, service layer can substitute {$gallery$} with #Html.RenderAction("Show", "Gallery", {id=5})
<div class="text">
<h1> title </h1>
<p> this is some random text {$gallery$} </p>
</div>
From your statement
Simplified problem is how to call render action from database acquired
string.
I get that you want to call an action using dynamically provided action-name and controller. If this what you want you could get it using
ViewModel
public class MyViewModel{
public string Action {get;set;}
public string ControllerName {get;set;}
}
Controller
public class MyController : Controller{
public ActionResult MyView(){
return View(new MyViewModel
{ Action ="MyPartialView" , ControllerName = "my"});
}
public ActionResult MyPartialView(){
return PartialView();
}
}
View
#model MyView
....render stuff for the view
#{
Html.RenderAction(Model.Action,Model.ControllerName);
}

How to save data in two tables? (In Create)

Good Day,
I wonder how to save the information in a create.
#model Request.Models.Chamados
#model Request.Models.InteracoesChamados
#{
ViewBag.Title = "Create";
}
as shown in the two tables above only that of course does not work.
please give me an example of this because it confused me.
NOTE: So for clarity, I fill out a form and save to 2 tables when I hit save.
environment:
Windows 7,
Visual Studio 2010,
C #,
MVC3 + Razor Entity Framework
There seems to be a few things here but for starters, you can only declare one model per view.
You could create a ViewModel that has both of those above, e.g.
public class ChamodosViewModel{
public Chamados Chamados {get;set;}
public InteracoesChamados InteracoesChamados {get;set;}
}
and then in your view
#model ChamodosViewModel
Do not use the Domain model for your view. Create a new POCO class which is specific for your view. Let's call it ViewModel, in general.
public class ChamodoVM
{
[Required]
public string ChamdoName { set;get;}
[Required]
public string InteracoName { set;get;}
//other properties here as needed
}
Now in yout GET action create an object of this class and pass to the View method.
public ActionResult Create()
{
var vm=new ChamodoVM();
return View(vm);
}
Make your view strongly typed to the ViewModel class.
#model ChamodoVM
#using(Html.BeginForm())
{
#Html.LabelFor(x=>x.ChamodoName)
#Html.TextBoxFor(x=>x.ChamodoName)
#Html.LabelFor(x=>x.InteracoName)
#Html.TextBoxFor(x=>x.InteracoName)
<input type="submit" />
}
When user submit the form, read the values from view model and assign it to an object of your domain modal and save. Thanks to MVC model binding. :)
[HttpPost]
public ActionResult Create(ChamodoVM model)
{
if(ModelState.IsValid)
{
var domainModel=new Chamodo();
domainModel.Name=model.ChamodoName;
domainModel.Interaco=new Interaco();
domainModel.Interaco.Name=model.InteracoName;
yourRepositary.SaveClient(domainModel);
//If saved successfully, Redirect to another view (PRG pattern)
return RedirectToAction("ChamodoSaved");
}
return View(model);
}

Linking Controller behaviour in MVC 3

Is it possible from a Controller to show a view, and then dependant on what that user selects in dropDownList - render another different view back in the original calling controller? Kind of a "daisy-chaining" effect.
The thinking behind this - is a user selecting a vehicle type - (associated with an ID number) in a view, back in the Controller dependant on what was chosen will render another view immediately displaying HTML according to the vehicle type they chose e.g. an HTML page for car or a boat or aeroplane etc...
If this is possbile can someone point me to a code examaple?
Actual Database Model below - but it is for documents, not vehicles!
check the method paremetares of your action method and return different views baed on that . Something like this.
public ActionResult GetInfo(string id,string vehicleTypId)
{
if(String.IsNullOrEmpty(vehicleTypeId))
{
var vehicle=GetVehicleType(vehicleTypId);
return View("ShowSpecificVehicle",vehicle) ;
}
var genericVehicle=GetVehicle(id);
return View(genericVehicle);
}
EDIT : Saying so, I seriously think you should keep those in 2 seperate Action methods. That makes your code clean and better readable. You may move the common functionality to a function and call if from bothe the action methods id needed. So i would do it in this way
Assuming you have a ViewModel for the first page( displays all vehicletypes)
public class VehicleTypesViewModel
{
//other relevant properties
public IEnumerable Types { set;get;}
public int SelectedTypeId { set;get;}
}
Your GET request for the initial view will be handled by this action result.It gets all the Vehicle types and return that to your view in the ViewModels Types property.
public ActionResult VehicleTypes()
{
VehicleTypesViewModel objVM=new VehicleTypesViewModel();
objVM.Types=dbContext.VehicleTypes.ToList();
return View(objVM);
}
and in your View called VehicleTypes.cshtml,
#model VehicleTypesViewModel
#using(Html.BeginForm())
{
#Html.DropDownListFor(Model.SelectedTypeId,new SelectList(Model.Types,"Text",Value"),"Select")
<input type="submit" value="Go" />
}
Another Action method to handle the form post. You have the selected type id here and you can get the specific details here and return a different view
[HttpPost]
public ActionResult VehicleTypes(VehicleTypesViewModel model)
{
// you have the selected Id in model.SelectedTypeId property
var specificVehicle=dbContext.Vehicles.Where(x=>x.TypeId=model.SelectedTypeId);
return View("SpecificDetails",specificVehicle);
}
Alternatively you can do a Get request for the specific vehicle using RedirecToAction method. I would prefer this approach as it sticks with the PRG pattern.
[HttpPost]
public ActionResult VehicleTypes(VehicleTypesViewModel model)
{
int typeId=model.SelectedTypeId;
return RedirectToAction("GetVehicle",new {#id=typeId});
}
public ActionResult GetVehicle(int id)
{
var specificVehicle=dbContext.Vehicles.Where(x=>x.TypeIdid);
return View(specificVehicle);
}
With Javascript : You can do a get call to the new view from your javascript also. without the HTTPpost to controller. You should add some javascript in your initial view for that
#model VehicleTypesViewModel
//Include jQuery library reference here
#Html.DropDownListFor(Model.SelectedTypeId,new SelectList(Model.Types,"Text",Value"),"Select")
<script type="text/javascript">
$(function(){
$("#SelectedTypeId").change(){
window.location.href="#Url.Action("GetVehicle","Yourcontroller")"+"/"+$(this).attr("id");
});
});
</script>
I think to get a better user experience create a partial view, and load that partial view in a div in the same page via an ajax call.
public ActionResult GetVehicalInfo(string id, string vehicleType)
{
var vehicle = GetVehicleType(id, vehicleTypId);
return PartialView("vehicle);
}

Categories

Resources