How can I make ViewContext available in my viewModel? - asp.net-mvc

In my controller I have the following code:
var viewModel = new ListCityViewModel {
City = rowData,
Meta =
{
DataSourceID = dataSourceID,
Em0 = em0
}
};
In my viewModel I have the following:
public class ListCityViewModel : BaseViewModel
{
public ListCitiesViewModel()
{
Meta = new Meta
{
Title = ViewContext.Controller.ValueProvider.GetValue("controller").RawValue +
ViewContext.Controller.ValueProvider.GetValue("action").RawValue,
Desc = ViewContext.Controller.ValueProvider.GetValue("controller").RawValue +
ViewContext.Controller.ValueProvider.GetValue("action").RawValue
};
}
public ICollection<City> Cities { get; set; }
}
and:
public class BaseViewModel
{
public BaseViewModel()
{
}
public Meta Meta { get; set; }
}
However it's not working as I get a message:
Error 6 An object reference is required for the non-static field,
method, or property 'System.Web.Mvc.ControllerContext.Controller.get'
Can anyone help me with this one. Do I need to pass something to the viewModel from the controller and how can I pass it. I have this viewModel common to many actions so I would like this to be automatic rather than me having to specify in the controller the controller name and action name.

In short : do not do that. It is not the right thing to do in MVC pattern. Your viewmodels should be as dumb as possible, and without any "contexts". If you need some "meta" data in your view models, depending for example on route data (action, controller), write a custom filter, which will put it there in OnActionExecuted - for example look by reflection if the current viewmodel has your "meta" properties (by this you make your own convention) and fill them from route data.

Related

Asp.Net MVC 5 How to send ViewBag to Partial View

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())

mvc pass tuple data as parameter

I have a tuble like this as model.
#model Tuple<Urun,List<UrunKatagori>>
inside the view I need to pass those data to controler.
here is the my button.
Html.X().Button().Text("Guncelle").Icon(Icon.PageSave)
.DirectEvents(de =>
{
de.Click.Url = "Urunler/Guncelle";
de.Click.ExtraParams.Add(new Parameter { Name = "Urun", Value ="Model.Item1", Mode = ParameterMode.Raw });//Iguess here is wrong
})
and my controller
[HttpPost]
public ActionResult Guncelle (Urun Urun){
Urun_BLL urun_bll = new Urun_BLL();
// urun_bll.Update(mdl);
X.Msg.Notify(new NotificationConfig
{
Icon = Icon.Accept,
Title = "Working",
Html =Urun.Ad.ToString()//I need to get data here
}).Show();
return this.Direct();
}
I strongly suggest that you create a viewmodel class, rather then passing a Tuple e.g.
public class GuncelleViewModel
{
public Urun Urun { get ;set; }
public List<UrunKatagori>> UrunKatagori { get; set; }
}
Then you can pass that to the view like so:
[HttpPost]
public ActionResult Guncelle (Urun Urun)
{
Urun_BLL urun_bll = new Urun_BLL();
// urun_bll.Update(mdl);
X.Msg.Notify(new NotificationConfig
{
Icon = Icon.Accept,
Title = "Working",
Html =Urun.Ad.ToString()//I need to get data here
}).Show();
var viewModel = new GuncelleViewModel()
viewModel.Urun = Urun;
viewModel.UrunKatagori = // TODO - However you get the categories.
return View(viewModel);
// this.Direct(); What does this.Direct() do? Replace it with calling the view instead, much cleaner.
}
In the view, use the following model
#model GuncelleViewModel
Useing a viewmodel class, which is associated one-to-one with a view file (*.cshtml), is a very good practise. It can help keep your design clean and more flexible, rather then passing specific data types, such as Tuple.

How To Use an Attribute (maybe) to point at Layout Page I want

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.

Accessing group by fields from Controller to View in MVC 3

How can I write code in View so as to access the groupby fields in linq. Here the data is rendered through a web service.
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Find Member";
var obj = new SearchMemberServiceClient();
List<MemberProxy> members = obj.FindMember("Mason", "Birkes", "", "", "", "").Members;
var sorted = from a in members
orderby a.FirstName ascending
group a by new { a.FormattedFullName, a.PersonId, a.Associations, a.MembershipsProxy[0].MembershipId } into k
select new { formattedname = k.Key.FormattedFullName, id = k.Key.PersonId, assoc = k.Key.Associations, memprox = k.Key.MembershipId };
return View(sorted.ToList());
}
}
You are passing an anonymous object to your view. Anonymous objects are emitted as internal by the compiler. Internal classes can only be used within the same assembly. ASP.NET MVC views are dynamically compiled by the ASP.NET runtime in separate assemblies. This basically means that you cannot access the anonymous types created in your controller actions inside your views. As a consequence this means that you should absolutely never pass anonymous objects to your views. So if you cannot pass anonymous objects, well, pass a named object by creating one. In this case they will be called a view model. A view model is class that you specifically define to meet the requirements of your view.
So what are the requirements of your view is the first question you should ask yourself when designing an ASP.NET MVC application? Well, in this case you seem to need a couple of properties (formattedname, id, assoc and memprox). Great, let's write a view model:
// you probably want to find a more suitable name
public class MyViewModel
{
public int Id { get; set; }
public int MemProx { get; set; }
public string FormattedName { get; set; }
public IEnumerable<Association> Associations { get; set; }
}
and then have your action pass this view model to the view:
public ActionResult Index()
{
var obj = new SearchMemberServiceClient();
var members = obj.FindMember("Mason", "Birkes", "", "", "", "").Members;
IEnumerable<MyViewModel> sorted =
from a in members
orderby a.FirstName ascending
group a by new
{
a.FormattedFullName,
a.PersonId,
a.Associations,
a.MembershipsProxy[0].MembershipId
} into k
select new MyViewModel
{
FormattedName = k.Key.FormattedFullName,
Id = k.Key.PersonId,
Associations = k.Key.Associations,
MemProx = k.Key.MembershipId
};
return View(sorted.ToList());
}
OK, now you can strongly type your view to this view model and present the information that it contains however you want:
#model IEnumerable<MyViewModel>
#foreach (var item in Model)
{
<div>#item.FormattedName</div>
...
}

Solid approach to loading reference data into view models in ASP.NET MVC

I want a way to separate the loading of reference data into a view model from the controller. At the moment I have a view model with a property for the selected value and the reference data:
public IEnumerable<SelectListItem> DayTypes { get; set; }
public int DayTypeId { get; set; }
and the data is populated from the relevant repository in the controller action:
model.DayTypes = _dayTypeRepository.GetAll().ToSelectList(d => d.Description, d => d.Identifier.ToString());
I would like to change this because it pollutes the controller with lots of repositories and code that is not core to its concerns. All of these dependencies make unit testing the controller a pain.
One possible approach to solving this would be to make the view model class do the loading which would require a custom model binder to instantiate them using the IoC container to provide the repository dependency. Is this a good option?
Another approach that I think would be good is hinted at in CodeCampServer but is incomplete and commented out involving attributes on the field in the view model:
[SelectListProvided(typeof(AllDaysSelectListProvider))]
public IEnumerable<SelectListItem> DayTypes { get; set; }
however I am struggling to figure out how this could be implemented in a way that would not require some major replumbing of the MVC framework.
How do you solve this problem?
EDIT: I want to keep with strongly typed views and avoid stuffing the data into view data.
FURTHER EDIT: I would also like a solution that is ideally model independent, by which I mean if the same reference data is needed by multiple view models this can be achieved with a single piece of code. Matt's approach is interesting but is tightly coupled to the view model.
I would use a service layer which would return me a POCO object that I would map to a view model. So my controller action would look like this:
public ActionResult Index(int id)
{
var model = _service.GetModel(id);
var viewModel = Mapper.Map<Model, ViewModel>(model);
return View();
}
I also like using action filters to avoid the mapping code all over again so:
[AutoMap(typeof(Model), typeof(ViewModel))]
public ActionResult Index(int id)
{
var model = _service.GetModel(id);
return View(model);
}
This way only the service talks with the CRUD repositories and the controller talks to the service and the mapping layer.
You could write a new ActionFilter that you can decorate an action method with; this action filter will load the reference data into the viewdata, which you can access from your view.
There is more on action filters here.
EDIT: Based on the users comments, this now includes a strongly typed option.
Firstly, you need to create the SharedViewModel to contain the shared data.
public class SharedViewModel
{
public List<string> Days { get; set; }
public List<string> Months { get; set; }
public List<string> Years { get; set; }
}
Next, we create the view model to be used by the Index view, which uses this shared view model.
public class HomeViewModel
{
public string ViewName { get; set; }
public SharedViewModel SharedViewModel { get; set; }
}
The next step is important, it implements an action filter called SharedData(), which will apply the shared data.
public class SharedDataActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var currentModel = ((HomeViewModel) filterContext.Controller.ViewData.Model);
currentModel.SharedViewModel = new SharedViewModel
{
Days = new List<string> {"Mon"},
Months = new List<string> {"Jan"},
Years = new List<string> {"2011"}
};
base.OnActionExecuted(filterContext);
}
}
At the moment, it just applies the whole shared data, but you can added parameters into the method to be selective.
When the action has been executed, this method takes the current model and adds the shared data.
Here is the controller action.
[SharedDataActionFilter]
public ActionResult Index()
{
return View("Index", new HomeViewModel { ViewName = "HomePage" });
}
You can access the data like any other strongly typed view, and the shared data wont affect the data already in the model (in this case "ViewName"). You can also use action filters across controllers, and globally across the site with mvc 3.
Hope this helps, Matt.

Resources