I know it's a bad practice to use database in the view. However, I'm also passing the User object and I wonder how I can make it easy to use.
I love the way it works in Ruby On Rails. You just create an #instance_variable in before_filter and call it from the controllers and from the views.
You can't do this in ASP.NET MVC though. So I created a class with all the data I need to pass to the view (DataContext and User):
public class XData
{
public DBDataContext DB { get; set; }
public User User { get; set; }
}
In controller's Initialize method I get all the data:
public XData X;
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
X = new XData();
X.DB = ...;
X.User = ....;
}
Works great: I can get the database object from the view like this:
<%= Model.X.DB.Users.First().Name %>
In order to pass the data to the view, I have to do the following:
public ActionResult Foo()
{
return View(new FooModel
{
X = X,
HelloMessage = "Hello world!"
});
}
The thing I don't like here is that I always have to write the X = X thing. How can I initialize that automatically?
Thanks
I've seen a similar problem where a lot of controllers return a similar model with only a few customizations. In this case, create an abstract base model that other models derive from, and a function that returns the particular model you need with the base X = X and so forth already set.
For example, consider this:
public abstract class MyBaseModel
{
public User User { get; set; }
}
public class FooModel : MyBaseModel
{
public string FooMessage { get; set; }
}
public class BarModel : MyBaseModel
{
public string BarMessage { get; set; }
}
public class MyController : Controller
{
public ActionResult Foo()
{
var model = this.GetModel<FooModel>();
// Set the properties on FooModel.
model.FooMessage = "Hello world!"
return View(model);
}
public ActionResult Bar()
{
var model = this.GetModel<BarModel>();
// Set the properties on BarModel.
model.BarMessage = "Hello world 2!"
return View(model);
}
protected T GetModel<T>() where T : MyBaseModel, new()
{
T model = new T();
// Set the properties on MyBaseModel.
model.User = ...;
return model;
}
}
If you want to share MyBaseModel and GetModel among many controllers, extract it out to a ModelProvider or something similar that is supplied to each controller, ideally through dependency injection.
You could place X in ViewData and write an HtmlHelper extension method to access X or override the View method of the controller and add a little reflection logic that maps every instance property of the controller to properties with matching names of your model (I guess Automapper could help here...)
I think you'll need to start by making your view strongly typed:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Namespace.XData>" %>
and in your controller:
public ActionResult Foo()
{
var X = new XData();
X.User = ....;
X.SomeProperty = ...;
X.Message = "Hello world!";
return View(X);
}
which allows you to access the object in your view like so:
<%: Model.User.UserName %>
<%: Model.Message %>
Related
I want to pass more than one object in the view. I have two object. One name is "Caller" and one Name is "Receiver". I am new in MVC. This is my action method.
public ActionResult IsActiveCaller(int id)
{
var caller = new CallerService().getCallerById(id);
if(caller.active)
{
var reciver= new reciverService().getReviverTime(caller.dialNo);
return (caller ) // here i also want to send reciver to view
}
return View();
}
Is there any way to send more than object in view?
Yes you can do this. There are multiple ways to do this.
1) You can use viewBag to pass the data or object into view.
You can see here to see how to use viewBag in mvc
2) you can use ViewData but it is not a good approach.
3) you can make ViewModel like as below (recomended)
public class callerReciver
{
public Caller caller {set;get;}
pblic Reciver eciver {set;get;}
}
Now pass callerReciver to view.You can access both object.hope you will understand.
4) Another way is to use partial view.You can make partial view to use more than one object in same view.
You can use a View Model:
public class MyViewModel
{
public Caller Caller { get; set; }
public Receiver Receiver { get; set; }
}
Then you can populate the view model this way:
public ActionResult IsActiveCaller(int id)
{
var caller = new CallerService().getCallerById(id);
var vm = new MyViewModel {
Caller = caller
};
vm.Receiver = caller.active ? new reciverService().getReviverTime(caller.dialNo) : null;
return View(vm);
}
View:
#model MyViewModel
<h1>#Model.Caller.Title</h1>
#if(Model.Receiver != null) {
<h1>#Model.Receiver.Title</h1>
}
The cleanest way is to pass by a view model :
ViewModel
public class MyViewModel {
public Caller MyCaller { get;set; }
public Receiver MyReceiver { get;set; }
}
Controller
public ActionResult IsActiveCaller(int id)
{
var caller = new CallerService().getCallerById(id);
var viewModel = new MyViewModel();
viewModel.MyCaller = caller;
if(caller.active)
{
var reciver= new reciverService().getReviverTime(caller.dialNo);
viewModel.MyReceiver = reciver;
}
return View(viewModel);
}
View
#model MyViewModel
<h1>#Model.MyCaller.Id</h1>
<h1>#Model.MyReceiver.Id</h1>
I have a _LoginPartial View and want to send data to it by ViewBag, but the Controller that I'am sending data from, doesn't have a View.
public PartialViewResult Index()
{
ViewBag.sth = // some data
return PartialView("~/Views/Shared/_LoginPartial.cshtml");
}
This code didn't work for me.
It seems you're expecting this Index action to be called when you do: #Html.Partial('_LoginPartial'). That will never happen. Partial just runs the partial view through Razor with the current view's context and spits out the generated HTML.
If you need additional information for your partial, you can specify a custom ViewDataDictionary:
#Html.Partial("_LoginPartial", new ViewDataDictionary { Foo = "Bar" });
Which you can then access inside the partial via:
ViewData["Foo"]
You can also use child actions, which is generally preferable if working with a partial view that doesn't need the context of the main view. _LoginPartial seems like a good candidate, although I'm not sure how exactly you're using it. Ironically, though, the _LoginPartial view that comes with a default MVC project with individual auth uses child actions.
Basically, the code you have would already work, you would just need to change how you reference it by using Html.Action instead of Html.Partial:
#Html.Action("Index")
Notice that you're calling the action here and now the view.
You can always pass data directly to the partial view.
public PartialViewResult Index()
{
var data = // some data
return PartialView("~/Views/Shared/_LoginPartial.cshtml", data);
}
Pass multiple pieces of data
public class MyModel
{
public int Prop1 { get; set; }
public int Prop2 { get; set; }
}
public PartialViewResult Index()
{
var data = new MyModel(){ Prop1 = 5, Prop2 = 10 };
return PartialView("~/Views/Shared/_LoginPartial.cshtml", data);
}
I passed viewBag data to my partial view like below, and I converted that viewBag data object to JSON in my partial view by using #Html.Raw(Json.Encode(ViewBag.Part));
my code sample is given below.
public async Task<ActionResult> GetJobCreationPartialView(int id)
{
try
{
var client = new ApiClient<ServiceRepairInspectionViewModel>("ServiceRepairInspection/GetById");
var resultdata = await client.Find(id);
var client2 = new ApiClient<PartViewModel>("Part/GetActive");
var partData = await client2.FindAll();
var list = partData as List<PartViewModel> ?? partData.ToList();
ViewBag.Part = list.Select(x => new SelectListItem() {Text = x.PartName, Value = x.Id.ToString()});
return PartialView("_CreateJobCardView" ,resultdata);
}
catch (Exception)
{
throw;
}
}
Here i have passed both model and viewBag .
First off, the code in your question does not run. When you do #Html.Partial("_SomeView") the Index() method you have there does not run. All #Html.Partial("_SomeView") does is render _SomeView.cshtml in your current view using the current view's ViewContext.
In order to get this to work you need a bit of functionality that's common to all the controllers in your project. You have two options: extension method for ControllerBase or a BaseController that all the controllers in your project inherit from.
Extension method:
Helper:
public static class ControllerExtensions
{
public static string GetCommonStuff(this ControllerBase ctrl)
{
// do stuff you need here
}
}
View:
#ViewContext.Controller.GetCommonStuff()
BaseController
Controller:
public class BaseController : Controller
{
public string GetCommonStuff()
{
// do stuff you need here
}
}
Other controllers:
public class SomeController : BaseController
...
...
View:
#((ViewContext.Controller as BaseController).GetCommonStuff())
I've got an ASP.NET MVC4 project with standard controllers and views. I have to different master pages I use, depending on a global variable I can reach out and get based on the Request.Url.Host. I've written the code below but it is getting kind of bulky to put in every controller. I've gotten it pretty short but was hoping for a suggestion to make it much cleaner.
private ActionResult IndexBase(string year)
{
var data = null; // real data here for model
var localConfig = LocalConfig.GetLocalValues(Request.Url.Host, null, year);
ViewResult view = localConfig.EventType == "svcc"
? View("Index", "~/Views/Shared/_Layout.cshtml", data)
: View("Index", "~/Views/Shared/_LayoutConf.cshtml", data);
return view;
}
I don't know if this solution works for you, but I would solve it with ViewModel's and a common base controller.
One of the nice things with Layouts is you can pass a base ViewModel with the properties common to all your pages (the users name, for example). In your case, you could store the path to the Layout.
First, the base class every ViewModel derives from:
public class MasterViewModel
{
public string Name { get; set; }
public string Layout { get; set; }
}
I prefer to use a 1:1 mapping of ViewModels to Views. That is, each action gets it's own ViewModel. For example: HomeIndexViewModel for /Home/Index, ProfileEditViewModel for /Profile/Edit, etc.
public class HomeIndexViewModel : MasterViewModel
{
// properties you need for /Home/Index
}
To simplify creating the ViewModels, I add a generic method on a base controller that handles setting all these the common properties:
public class BaseController : Controller
{
protected T CreateViewModel<T>() where T : MasterViewModel, new()
{
User user = db.GetUser(User.Identity.Name);
var localConfig = LocalConfig.GetLocalValues(Request.Url.Host, null, year);
return new T()
{
Name = user.Name,
Layout = localConfig.EventType == "svcc" ? "~/Views/Shared/_Layout.cshtml"
: "~/Views/Shared/_LayoutConf.cshtml"
}
}
}
And finally, just use CreateViewModel() in each of your Actions and things should work:
public class HomeController : BaseController
{
public ActionResult Index()
{
HomeIndexViewModel viewModel = CreateViewModel<HomeIndexViewModel>();
return View(viewModel);
}
}
Inside the Views, you can just set
#model HomeIndexViewModel
#{
Layout = Model.Layout;
}
There's no need to duplicate the path anywhere, and changing the logic on which Layout to show requires you only change it in one place.
Is it a code smell to have to following pattern, given the following code (highly simplified to get straight to the point) ?
The models :
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public Category Cat { get; set; }
}
class Category
{
public int Id { get; set; }
public string Label { get; set; }
}
The view to edit a Product :
<% =Html.EditorFor( x => x.Name ) %>
<% =Html.EditorFor( x => x.Category ) %>
The EditorTemplate for Category
<% =Html.DropDownList<Category>() %>
The HtmlHelper method
public static MvcHtmlString DropDownList<TEntity>(this HtmlHelper helper)
where TEntity : Entity
{
var selectList = new SelectList(
ServiceLocator.GetInstance<SomethingGivingMe<TEntity>>().GetAll(),
"Id", "Label");
return SelectExtensions.DropDownList(helper, "List", selectList, null, null);
}
For information, the real implementation of the helper method takes some lambdas to get the DataTextField and DataValueField names, the selected value, etc.
The point that bothers me is using a servicelocator inside the HtmlHelper. I think I should have a AllCategories property in my Product model, but I would need to be populated in the controller every time I need it.
So I think the solution I'm using is more straightforward, as the helper method is generic (and so is the modelbinder, not included here). So I just have to create an EditorTemplate for each type that needs a DropDownList.
Any advice ?
IMHO I'd leave it the way it is, have the same thing in another project.
BUT the service location bothered me as well so for another project I made this part of an ActionFilter which scans a model, finds all the anticipated dropdowns and does a batch load into ViewData. Since the ServiceLocator or Repository/Context/whatever is already injected into the Controller you don't have to spread your service location all over the place.
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
foreach( var anticipated in SomeDetectionMethod() )
{
var selectList = new SelectList(
ServiceLocator.GetInstance<SomethingGivingMe<TEntity>>().GetAll(),
"Id", "Label");
ViewData["SelectList." + anticipated.Label/Name/Description"] = selectList;
}
}
In the view you can then make a helper to load up those dropdowns via a custom editor template or other method.
advice: look at the asp.net mvc sample application from here: http://valueinjecter.codeplex.com/
good luck ;)
This is how ValueInjecter's Sample Application could get the dropdowns:
(but it doesn't right now cuz I'm ok with the Resolve thing)
public class CountryToLookup : LoopValueInjection<Country, object>
{
ICountryRepo _repo;
public CountryToLookup(ICountryRepository repo)
{
_repo = repo;
}
protected override object SetValue(Country sourcePropertyValue)
{
var value = sourcePropertyValue ?? new Country();
var countries = _repo.GetAll().ToArray();
return
countries.Select(
o => new SelectListItem
{
Text = o.Name,
Value = o.Id.ToString(),
Selected = value.Id == o.Id
});
}
}
Imagine I have a list of objects that implement an interface called ISummary
The objects within this list MAY have additional properties ie.
public interface ISummary {
Guid Id {get;set;}
string Title {get;set;}
DateTime Created {get;set;}
}
public class GigSummary: ISummary {
Guid Id {get;set;}
string Title {get;set;}
DateTime Created {get;set;}
string VenueName {get;set}
string Band {get;set;}
}
public class NewsSummary: ISummary {
Guid Id {get;set;}
string Title {get;set;}
DateTime Created {get;set;}
string Author{get;set}
}
I now pass this list of Gigs and News Summary objects (as a list of ISummary) to the view as the model.
I want to render this list using a different partial for each type contained in the list.
How can I do this is ASP.NET MVC?
The most obvious way I can think of would be to do something like:
foreach(ISummary summ in listOfISummary) {
Html.RenderPartial(String.Fomat("~/Views/Shared/{0}Renderer.ascx", summ.GetType.ToString()), summ, ViewData);%>
}
and create a strongly typed view with a naming convention, like NewsSummaryRenderer.ascx.
I expect that you could move this out to a helper method though, but I'd add it to one of the existing helpers through an extension method rather than putting it in a code behind as suggested previously.
You could put a helper method in the view's codebehind, and then do something like:
Type modelType = this.Model.GetType();
if (modelType == typeof(NewsSummary)) this.RenderPartial("newspartial", this.Model as NewsSummary);
else if (modelType == typeof(GigSummary)) this.RenderPartial("gigpartial", this.Model as GigSummary);
Lewis is on the right track. I would take a slightly different tack--have both of the "widgets" extend from a common base class which provided information about the view names involved. Then add an extension method to your page class to "render widget" which could get the appropriate view in place.
Check out the Kona ASP.NET MVC sample app for a working example of this concept.
I'd create an HtmlHelper extension that did this. Here's some pseudocode that looks shockingly like c# and may actually work:
public static void TemplatedList<T>(this HtmlHelper me, IEnumerable<T> items,
IDictionary<Type, Action<T>> templates)
{
foreach(var item in items)
{
var template = templates[item.GetType()];
if(template != null) template(item);
}
}
I'd use it like this:
<% HtmlHelper.TemplatedList(ViewData.Model, new Dictionary
{
{typeof(GigSummary), x => %>
<div class="gigSummary">
SUP! GIG ANNOUNCEMENT FOR <%= x.Band %>!!
What: <%= x.Title %>
When: <%= x.Created %>
Who: <%= x.Author %>
</div>
<%}
// add more type/template pairs here
}); %>
Here's a simple extension method you can create to extract just the types you need:
public static class Extensions
{
public static IEnumerable<U> ExtractOfType<U, T>(this IEnumerable<T> list)
where T : class
where U : class
{
foreach (var item in list)
{
if (typeof(U).IsAssignableFrom(item.GetType()))
{
yield return item as U;
}
}
}
}
Test:
public interface IBaseInterface
{
string Foo { get; }
}
public interface IChildInterface : IBaseInterface
{
string Foo2 { get; }
}
public interface IOtherChildIntreface : IBaseInterface
{
string OtherFoo { get; }
}
public class BaseImplementation : IBaseInterface
{
public string Foo { get { return "Foo"; } }
}
public class ChildImplementation : IChildInterface
{
public string Foo2 { get { return "Foo2"; } }
public string Foo { get { return "Foo"; } }
}
public class OtherChildImplementation : IOtherChildIntreface
{
public string OtherFoo { get { return "OtherFoo"; } }
public string Foo { get { return "Foo"; } }
}
....
List<IBaseInterface> b = new List<IBaseInterface>();
b.Add(new BaseImplementation());
b.Add(new ChildImplementation());
b.Add(new OtherChildImplementation());
b.Add(new OtherChildImplementation());
foreach (var s in b.ExtractOfType<IOtherChildIntreface, IBaseInterface>())
{
Console.WriteLine(s.GetType().Name);
}
This will get all of the items in the list that are of the derived type you're looking for. So, in your controller, pass in the entire list to the view. Then, have partial views that take IEnumerable's of the type that partial needs, and within your main view, call this extension method and pass on the result to those individual partial views.