Pass variable back from EditorTemplate to page or layout - c#

I am tired to google around and ddn't found the exact answer.
I know I can pass date from view to template by additionalViewData parameter of .EditorFor() or .DisplayFor() but I want a data back from template!
I have Date.cshtml in my ~/Views/Shared/EditorTemplates/.
My model property already have something like this:
public class Person
{
[DisplayFormat(DataFormatString = "{0:d}")]
[DataType(DataType.Date)]
public Nullable<DateTime> BirthDate { get; set; }
}
and the view:
#Html.EditorFor(model => model.BirthDate)
Now I want only to load datepicker script when ever the view uses the date template.
So I try to use RequireDatePicker like this in my template:
#model DateTime?
#Html.TextBox(...)
#{ViewBag.RequireDatePicker = true;}
and in the bottom of _Layout.cshtml:
#Scripts.Render("~/bundles/jquery")
#if (ViewBag.RequireDatePicker == true)
{
<script type="text/javascript">
if (!Modernizr.inputtypes.date) {
// Todo: use Modernizr.load
$(function () {
$("input[type='date']").datepicker();
});
}
</script>
}
The problem is I just realize ViewBag/ViewData has a different context from the view/partial and the display/editor template. What is the best way to do this?
Fix
I can pass data from template back to view by route data like this:
#{ViewContext.RouteData.DataToken["requireDatePicker"] = true;}
However I end-up not passing the data but just this:
Have bundle "~/bundles/jqueryui" that
remove jquery ui date picker feature from it (and move to "~/bundles/jqueryui-datepicker")
include a Modernizr.inputtypes.date check and Modernizr.load to load "~/bundles/jqueryui-datepicker"
No need to modify _layout.cshtml to check for RequireDatePicker. #Scripts.Render("~/bundles/jquery") and #Scripts.Render("~/bundles/jqueryui") is enough sinec Modernizr.load is already included.
No need to pass RequireDatePicker from Date.cshtml to view or _Layout.cshtml as Modernizr.load

You could create dynamic javascript bundles like outlined in the following article
http://neimke.blogspot.co.nz/2011/12/composing-child-views-with-bundles.html
basically your editorfor template calls the following code
#{
var bundle = Microsoft.Web.Optimization.BundleTable.Bundles.GetRegisteredBundles()
.Where(b => b.TransformType == typeof(Microsoft.Web.Optimization.JsMinify))
.First();
bundle.AddFile("~/Scripts/DatePicker.js");
}
but you may want to replace the Where clause with one that finds a specific bundle instead, maybe one specifically for including javascript for child views.
If you wanted to go nuts, you could probably extend the razor view engine or WebViewPage to look for matching View.cshtml.js to every view that is rendered and add it to a bundle.

Related

Render custom controls in mvc in C#

I have a need to display many different data types on an MVC page using C#.
What is the best way to code this up. I have 200 items with 10-15 different data types I want to be able to edit.
I could put all of the code in the cshtml but would prefer to, as I have with the original php code, call a function to render the controls.
if(controlType = "checkBox"
{
...
}
else if(controlType = "string"
{
...
}
else if(controlType = "myClassType"
}
Thanks
Use Html.EditorFor. It will look for a matching template depending on the datatype of the property in the ViewModel in the Views\Shared\EditorTemplates folder.
For example, you can define a template Boolean.cshtml to render a checkbox for boolean values of your model.
See Using Display Templates and Editor Templates in ASP.NET MVC.
The EditorFor() helper displays a user interface for editing a model property. This user interface is known as Editor Template.
Check the below example for more details,I have Index() action,,return a list of object with different datatypes
public ActionResult Index()
{
NorthwindEntities db=new NorthwindEntities();
Employee emp = db.Employees.Find(1);
return View(emp);
}
The preceding code retrieves an Employee from the database whose EmployeeID is 1. It then passes this Employee object to the Index view. The Index view contains the following markup:
#model Demo.Models.Employee
#{
Layout = null;
}
...
<body>
#using(Html.BeginForm())
{
#Html.DisplayFor(m=>m.EmployeeID)
#Html.DisplayFor(m=>m.FirstName)
#Html.DisplayFor(m=>m.BirthDate)
}
</body>
</html>
It then uses the DisplayFor() helper to show three model properties
EmployeeID, FirstName, and BirthDate.

Render part of page on dropdown selection part 2

This is a follow on to similar question but taking suggestions into account.
Render part of page on dropdown selection
I have a chart on my main view which I would like to update partially when a dropdown selects different values.
The page renders correctly the first time, but when I select a new value in the dropdown, then I think the .submit script is failing in the script .submit() because when I put a break on window.submitAjaxForm it is never reached.
_PnlChart.cshtml
<img src="#Url.Action("CreateTraderPnlChart3")" width="600" height="600" align="middle" vspace="50" />
My mainview Index.cshtml:
<div class="w3-half">
<div id="ExportDiv">
#{ Html.RenderPartial("_PnlChart");}
</div>
#using (Ajax.BeginForm("GetEnvironment",
new RouteValueDictionary { { "Environment", "" } }, new AjaxOptions() { UpdateTargetId = "ExportDiv" }, new { id = "ajaxForm" } ))
{
#Html.DropDownList("PeriodSelection",
new SelectList((string[])Session["Periods"]),
(string)Session["Period"],
new
{ onchange = "submitAjaxForm()" })
}
</script>
<script type="text/javascript">
$('form#ajaxForm').submit(function(event) {
eval($(this).attr('onsubmit')); return false;
});
window.submitAjaxForm = function(){
$('form#ajaxForm').submit();
}
</script>
</div>
My controller:
public ActionResult PeriodSelection(string dropdownlistReturnValue) // dont know what dropdownlistReturnValue is doing?
{
Session["Period"] = dropdownlistReturnValue;
return PartialView("~/Views/Employee/_PnlChart.cshtml");
}
This line in your code,
eval($(this).attr('onsubmit')); return false;
I am not sure what you were intending to do here. But from your question, i assume you wanted to do a form submission. But that line will not submit the form. The expression $(this).attr('onsubmit') is going to return undefined as your form does not have an onsubmit attribute defined.
But you already have the form submit code in your other method (submitAjaxForm). So if you simply remove the $('form#ajaxForm').submit handler (apparently it does not do anything useful), your code will work. When you change the dropdown, it will make an ajax form submission.
But your form action is set to GetEnvironment action method. That means your ajax form submission will be to that action method. In your question you have a different action method which returns the updated chart content. It does not makes sense!
I personally prefer to write handwritten ajax calls instead of relying on the ajax action helper methods. The below is the code i would probably use (Except the dropdownlist code. read further)
<div id="ExportDiv">
#{ Html.RenderPartial("_PnlChart");}
</div>
#Html.DropDownList("PeriodSelection",
new SelectList((string[])Session["Periods"]),
(string)Session["Period"], new
{ data_charturl = Url.Action("PeriodSelection","Home")})
Now listen to the change event of the SELECT element.
$(function(){
$("#PeriodSelection").change(function(){
var v = $(this).val();
var url=$(this).data("charturl")+'?dropdownlistReturnValue='+v;
$("#ExportDiv").load(url);
});
});
You should consider using the a view model to pass the Dropdownlist data. Why not use the DropDownListFor helper method ? It looks much clean, Mixing a lot of C# code (See all the session casting and all.) makes it kind of dirty IMHO.

Easiest way to update an MVC View from a Controller

I am totally new to .NET's MVC framework and have a scarce understanding of how the Model-View-Controller architecture works. I have a simple and basic problem.
In my application, it is a calendar app, I have a Controller named EventsController which overrides a method named OnCommand, which is a part of the DayPilot Calendar(open source calendar). Here's my method:
protected override void OnCommand( CommandArgs e )
{
switch (e.Command)
{
case "previous":
StartDate = StartDate.AddMonths(-1);
StrMonth = CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedMonthName(StartDate.Month);
Console.WriteLine(StrMonth);
Query();
Update(CallBackUpdateType.Full);
break;
case "next":
StartDate = StartDate.AddMonths(1);
StrMonth = CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedMonthName(StartDate.Month);
Console.WriteLine(StrMonth);
Query();
Update(CallBackUpdateType.Full);
break;
}
}
When a forward and backwards arrow are clicked at the top of the calendar - the previous and next are called and StrMonth is set to the correct month. There is a div containing the name of the month between the two arrows, it needs to update from the Controller Method or another. How do I update the view from the Controller to correctly change the month displayed at the top of the calendar?
I have tried the following:
-Adding a property to my Event Model named CurrentMonth and then using that variable inside of the div in the View like this:
<div>#Model.CurrentMonth</div>
and then attempting to use an ActionResult in my Controller that updates it for me and calls a Javascript in my View:
In the Controller:
public ActionResult UpdateMonth(Event model)
{
model.CurrentMonth = "changeMonth()";
//return View(model);
}
In the View:
<script type="text/javascript">
#Model.Item2.CurrentMonth;
function changeMonth() {
#ViewBag.strMonth = #Model.Item2.CurrentMonth;
}
</script>
This should be trivial, in almost every other client-server architecture, updating the client is much easier than in MVC. What is the simplest and most basic way to update the View based on a Controller ActionResult or method? Do I need to be using Ajax or the ViewBag/ViewData properties of MVC? An example would be greatly appreciated.

Losing ViewModel on Partial View post back to controller

I need to persist changes to a view model that contains a collection, but everytime i post back to the controller, i am losing my model bindings. I am fairly new to MVC so i may be missing something glaring here.
#{ Html.RenderAction("TabList", "TabController", new {Id = Model.Id}); }
I have a main container page that has a render action to a controller to return the first partial view.
[HttpGet]
public ViewResult TabList(Guid orderid)
{
// build the viewmodel
return View("ControlTabList", model);
}
From there iterate over the collection and different render partials based on the object type. (I have simplified the code here, as the items are polymorphic and have some type of downcasting)
#model TabListViewModel
#using (Html.BeginForm("UpdateItem", "TabController", FormMethod.Post, new {Id = "myForm"}))
{
#Html.AntiForgeryToken()
<input type="submit" value="Send" id="submitButton"/>
#for (int i = 0, c = this.Model.Count; i < c; i++)
{
var currentItem = this.Model.ElementAt(i);
#switch (currentItem.Code)
{
case "1":
Html.RenderPartial("Partials/ItemOne", currentItem);
break;
case "2":
Html.RenderPartial("Partials/ItemTwo",currentItem);
break;
default:
Html.RenderPartial("Partials/ItemThree",currentItem);
break;
}
}
}
When I post back to the controller my ViewModel will always be null.
[HttpPost]
public ActionResult UpdateItems(TabListViewModel model)
{
/* i will remove the redirect here, as the model above is always null*
}
Is there a reason why i am losing the bindings? I would like to save the entire collection, instead of individually saving each item in the collection.
There are 2 reasons why your implementation will fail.
First is the use of partials to display each item in the collection. If you inspect the html your generating you will see that the id and name attributes for each currentItem are identical. Duplicate id's are invalid html and duplicate name attributes means that you cannot bind back to a collection. Assuming currentItem has a property string Name, then the correct name would be <input name="Name[0]../>, <input name="Name[1]../> etc. Note the indexers in the name attribute, which allow you to bind to a collection.
Second, your TabListViewModel appears to be collection of a base type where you have added derived types. When you post back, the DefaultModelBinder will only initialize items of the base type since it has no way of knowing which derived type to initialize. From your last question, I assume the base type is ProvinceViewModel and the derived types are QuebecViewModel and OntarioViewModel. Since ProvinceViewModel is abstract, it cant be initialized (no constructor) so your model will always be null. While it is possible to write a custom abstract ModelBinder, as a self confessed newbie, this might be best left until you have a better knowledge of MVC and the model binding process (this article will help you get started)
The easiest way to solve this is with a view model containing collections of each type and use for loops or custom EditorTemplates. For example
View model
public class ProvinceVM
{
public List<QuebecViewModel> QuebecProvinces { get; set; }
public List<OntarioViewModel> OntarioProvinces { get; set; }
}
Then create an EditTemplate for each type
In /Views/Shared/EditorTemplates/QuebecViewModel.cshtml
#model QuebecViewModel
#Html.TextBoxFor(m => m.someProperty)
....
Then in the main view
#model ProvinceVM
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.QuebecProvinces)
// Ditto for OntarioProvinces, or you can use a `for` loop as follows
for(int i = 0; i < Model.OntarioProvinces.Count; i++)
{
#Html.TextBoxFor(m => m.OntarioProvinces[i].someProperty)
....
}
}
Note that both options will generate controls such as
<input name="QuebecProvinces[0].someProperty" ..../>
<input name="QuebecProvinces[1].someProperty" ..../>
which will be correctly bound when you post back to
public ActionResult UpdateItem(ProvinceVM model) // suggest you use a more appropriate name (at least pluralize it)
There seems to be way too much logic in the view. MVC was created to make use of Soc - separation of concerns ( however the view itself it not directly an observable object from the model ) . This enables a developer to write clear and precise code for each part of the system. It however, because of its flexibility as a framework , makes the decision of this up to the developer.
The idea is for clean views, light controllers and heavy classes.
You seem to be running in circles a bit here. From your code it seems to display a view that then returns a view that renders a partialview based on a parameter that is sent to it by the controller (which is building the viewmodel for the partialview ) which is received by the render action id property.
I think there are some clear considerations that need to be implemented for the codes maintainability
First off. It seems as though you want to achieve the display of a partialview. This partialview is determined based on a parameter, the Model.Id . You do not say how this parameter is being injecting into the RenderAction but that should not matter.
#{ Html.RenderAction("TabList", "TabController", new {Id = Model.Id}); }
This code above does not need to exist. It is calling itself. And if that is not the case ( i might not fully understand why you have that there ) then that should be replaced with the call to the partial view, With the main view that the code is sitting on being injected with the ModelId parameter . In this use case that is the View TabList itself.
[HttpGet]
// Model id is passed into the controller in which ever fashion it was
// used to pass into the RenderAction method
public ViewResult TabList(int modelId = 3) // allows for a default on 3
{
// build the viewmodel
return View();
}
"Build the viewmodel" is where the main code happens, this is the stuff that determines what is going to be displayed, and so because it is not directly a display element it needs to sit in the controller. Think of it like this. When the view gets its model, everything the view needs to display should already be in the model. And If you are using polymorphism then there is no reason to be writing switch cases inside of for loops in a view . Just no. That should be sent off to its corresponding interfaces
So to build the viewmodel could be something like this
//build viewmodel
TabListViewModel model = new TabListViewModel{
PartialStuff = dbcontext.entity.FirstOrDefault(_ => _.ModelId == modelId),
}
So now you have a viewmodel with objects that has been filtered based on the modelId , and now just return that model.
return View(model);
The View then declares that model
#model TabListViewModel
And you have one partial view that receives only the objects it needs based on the model
#Render.PartialView("_ItemStuff",Model.PartialStuff)
The partial view then has its model defined as "Partialstuff" and the form objects are created by that model . This allows for a strongly typed model that will pass values back to the controller.
#model PartialStuff
#using (Html.BeginForm("UpdateItem", "TabController", FormMethod.Post, new{Id = "myForm"}))
{
#Html.AntiForgeryToken()
<input type="submit" value="Send" id="submitButton"/>
// Add values with the model directly attached
#Html.TextBoxFor(_ => _.stuffFromTheModel)
I updated the following code using the EditorTemplate to get rid of the heavy switch statements i was using to iterate over the polymorphic collection of derived types.
#for (int i = 0, c = this.Model.Count; i < c; i++)
{
var currentItem = this.Model.ElementAt(i);
#Html.EditorFor(model => currentItem)
}
I then added the following prefix in all of my EditorTemplates to prevent duplicate name and ids of the controls.
#{
ViewData.TemplateInfo.HtmlFieldPrefix = Model.ProvinceCode;
}
I created a custom ModelBinder to intercept and bind the form data to create a ViewModel i can use.
[HttpPost]
public ActionResult UpdateItem([ModelBinder(typeof(ProvinceCustomModelBinder))]ProvinceVM model)

Generating a list of RadioButtons with the same name in a for-loop in an ASP.NET MVC3 view

I'm working on an ASP.NET MVC3 project, and I have a problem with RadioButtons name generated by a for loop in a PartialView.
First, here is my code, I'll explain the exact problem just after :
Model FileUploadModel
public class FileUploadModel
{
public HttpPostedFileBase File { get; set; }
public bool IsMainFile { get; set; }
}
Model MyModel
public class MyModel
{
// Some properties
public List<FileUploadModel> Files { get; set; }
}
How the PartialView _UploadFiles is called
#Html.Partial("_UploadFiles", Model) // Model is a MyModel model
PartialView _UploadFiles - HERE IS THE PROBLEM
#model MyModel
#{
var nbFiles = (Model.Files != null ? Model.Files.Count : 0);
const int NB_DEFAULT_UPLOAD_FIELDS = 3;
}
#for (int i = 0; i < NB_DEFAULT_UPLOAD_FIELDS; i++)
{
#Html.TextBoxFor(m => m.Files[nbFiles].File, new { type = "file", name = "Files", id = "Files" })
#Html.RadioButtonFor(m => m.Files[nbFiles].IsMainFile, "Main ?", new { id = "rb" + i + "_File_IsMain" })
}
So this is the last snippet the problem. I'd like to create 3 upload fields with an associated RadioButton indicating if the file is the "main file". However, with the above snippet, the displayed view is OK, but when I validate my form, my posted model only have the first picture uploaded (and the corresponding boolean IsMainFile , the others are just ignored). That means the List Files contains only the first TextBox and first RadioButton data
So I tried with #Html.RadioButtonFor(m => m.Files[i].IsMainFile, but the RadioButtons have a different name so user can check all RadioButtons, which is NOT the desired behavior. MVC doesn't authorize me to override the name of the RadioButtons, so I can't give them my own name.
How can I generate these fields is order to only ONE Radiobutton can be checked AND my MyModel.Files property contains all chosen files ?
Thank you
I think your best bet here is to use editor templates.
First, you create a view named FileUploadModel, and place it a folder named EditorTemplates which should exist under your controller's Views folder. Note that the name of the view must match the name of your view model's class.
Its contents would look something like:
#model FileUploadModel
#Html.TextBoxFor(m => m.File, new { type = "file" })
#Html.RadioButtonFor(m => m.IsMainFile, true)
Then, back in your "_UploadFiles" view, your markup for the input field and radio button would change to:
#model MyModel
using (Html.BeginForm(...
#Html.EditorFor(m => m.Files)
...
Note that this will automatically iterate through and apply that editor template for each FileUploadModel object, and automatically name them as required. This of course assumes that the Files property in your model is populated.
Your action that accepts the post should accept type MyModel as its sole parameter, and everything should bind up automatically at post.
There are ways to avoid editor templates and build and name those fields programmatically, but this is really the preferred way, and much cleaner.
More specific to your issue... (In other words, I forgot that the code above won't group radio buttons.)
For the grouping of radio buttons to work, you should set the attribute name as follows:
#Html.RadioButtonFor(m => m.IsMainFile, true, new { Name = "IsMainFile" })
Case matters here. Note that Name must be capitalized for this to work. I'm not sure why this is the case.
The problem is that once you change that attribute, this field will no longer automatically bind in the post. This is unfortunate but understandably is as designed since, if you look at the request, I believe you will see that only the selected radio button value has been posted.
So, you could change the value of the radio button to something recognizable since just that one instance of IsMainFile will show up in the request, if I'm correct.
Perhaps you can use this specific knowledge to tweak your existing code.

Categories