Display what is in HTML.HiddenFor or Save Display - asp.net-mvc

Call me crazy but this seems incredibly redundant and unnecessary but for the life of me I can't find a function that achieves both.
using Html.BeginForm("Save, Home") {
#Html.DisplayFor(x => x.Id)
#Html.HiddenFor(x => x.Id)
#Html.DisplayFor(x => x.CreatedDate)
#Html.HiddenFor(x => x.CreatedDate)
//Useful fields below
}
Let me explain - I want to save the CreatedDate and Id values, hence the hiddenFor. This of course generates an input behind the scenes, but I also need to display it. DisplayFor, and ValueFor both return Id = 0 on my form submission. More specifically when I update the data in the form, I would like to retain those two values. The model returned has them at 0/Datetime year 0.
Is there not a way to save those values for the next model?

You could create an extension method:
public static class HtmlHelperExtensions
{
public static IHtmlString DisplayAndHiddenFor<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Func<TModel, TProperty> propertyAccessor)
{
return new HtmlString(
helper.DisplayFor(propertyAccessor).ToHtmlString()
+ helper.HiddenFor(propertyAccessor).ToHtmlString());
}
}
Used like this (with the proper using of course):
#Html.DisplayAndHiddenFor(x => x.Id)
#Html.DisplayAndHiddenFor(x => x.CreatedDate)

Related

Asp.Net Razor View Passing Expression To Partial

I find myself writing this a whole lot in my views:
<div class="form-group">
#Html.LabelFor(x => x.City)
#Html.EditorFor(x => x.City)
#Html.ValidationMessageFor(x => x.City)
</div>
I'd really like to put this in a Partial _Field.cshtml, something like this:
#model //what model type???
<div class="form-group">
#Html.LabelFor(Model)
#Html.EditorFor(Model)
#Html.ValidationMessageFor(Model)
</div>
That could then be called by:
#Html.Partial("_Field", x => x.City)
What would the #model type in my partial be if I wanted to accomplish something like this?
UPDATE This works, but I'd rather use a partial for ease of changing the template:
public static MvcHtmlString Field<TModel, TItem>(this HtmlHelper<TModel> html, Expression<Func<TModel, TItem>> expr)
{
var h = "<div class='form-group'>";
h += $"{html.LabelFor(expr)}{html.EditorFor(expr)}{html.ValidationMessageFor(expr)}";
h += "</div>";
return MvcHtmlString.Create(h);
}
That's not possible. However, what you want is very similar to editor templates. Essentially, you just create a view in Views/Shared/EditorTemplates named after one of the following conventions:
A system or custom type (String.cshtml, Int32.cshtml, MyAwesomeClass.cshtml, etc.)
One of the members of the DataType enum (EmailAddress.cshtml, Html.cshtml, PhoneNumber.cshtml, etc.). You would then apply the appropriate DataType attributes to your properties:
[DataType(DataType.EmailAdress)]
public string Email { get; set; }
Any thing you want, in conjunction with the UIHint attribute:
[UIHint("Foo")]
public string Foo { get; set; }
Which would then correspond to a Foo.cshtml editor template
In your views, then, you simply use Html.EditorFor:
#Html.EditorFor(x => x.City)
Then, for example, you could have Views/Shared/EditorTemplates/String.cshtml as:
<div class="form-group">
#Html.Label("", new { #class = "control-label" })
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { #class = "form-control" })
#Html.ValidationMessage("")
</div>
(The empty quotes are placeholders. Razor will automatically fill in the appropriate property name, thankfully.)
Calling EditorFor, then, will print all of this, rather than just the default text input. You can take this much further, as well. I have some articles on my blog that goes into greater detail, if you're interested.
UPDATE
It's worth mentioning a few features of EditorFor:
You can pass a template directly to the call, meaning you can customize what template is used on the fly and per instance:
#Html.EditorFor(x => x.City, "MyCustomEditorTemplate")
You can pass additionalViewData. The members of this anonymous object are added to the ViewData dynamic dictionary. Potentially, you could use this to branch within your editor template to cover additional scenarios. For example:
#Html.EditorFor(x => x.City, new { formGroup = false })
Then in your editor template:
#{ var formGroup = ViewData["formGroup"] as bool? ?? true; }
#if (formGroup)
{
<!-- Bootstrap form group -->
}
else
{
<!-- Just the input -->
}

Kendo UI Multiselect post data is not coming, list count=0

I am not able to get Kendo UI Multiselect's value in controller. it gives me length of list as 0. MultiSelect is as:
#(Html.Kendo().MultiSelectFor(model => model.Technicians)
.Name("Technicians")
.Placeholder("Select Technician(s)")
.DataTextField("Name")
.DataValueField("ID")
.Filter(FilterType.Contains)
.AutoBind(true)
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetTechnicians", "Project");
})
.ServerFiltering(false);
})
.BindTo(Model.Technicians)
)
Technicians is : public List Technicians{get; set;} in model
I ran into this problem as well.
We ended up creating an IEnumerable<int> of Ids in the viewmodel to store that value.
So, in our instance
public IEnumerable<PersonLookupViewModel> TaggedPersons { get; set; } (this is the list of people with IDs that I really want - contains first/last/id)
public IEnumerable<int> PeopleToTag { get; set; } (this is the IEnumerable Int of the view model solely for posting the ints to the controller)
My view looked like this:
<div class="editor-field">
#Html.LabelFor(model => model.taggedPersons)
#Html.ListBoxFor(model => model.PeopleToTag,
new SelectList(Model.CommitteeMembers, "PersonId", "FullName"))
</div>
and the JS wire up
$("#PeopleToTag").kendoMultiSelect({ "dataTextField": "FullName", "dataValueField": "PersonId", "placeholder": "Tag or Untag Persons to Action Item" });
So switching it to a ListBoxFor(model = > model.PeopleToTag) which was my Ienumerable field in my viewmodel solely to be able to get the data in the controller. I then used automapper to create the Person model I actually needed.
I'm not sure if this is the best way, but it worked for us.

Using #Html.DisplayNameFor() with PagedList

I've been trying out the PagedList package to get paging for my index views. Everything was going well, and at the controller level everything is working fine, it only displays 5 records per page, and displays the appropriate page based on the querystring.
My problem is in the view. I changed the #Model to PagedList.IPagedList so I could access the Model.HasNextPage and other properties, but now the #Html.DisplayNameFor(model => model.ItemName) are no longer working. I get this error:
PagedList.IPagedList<Dossier.Models.Item>' does not contain a definition for 'ItemName' and no extension method 'ItemName' accepting a first argument of type 'PagedList.IPagedList<Dossier.Models.Item>' could be found (are you missing a using directive or an assembly reference?)
Here are the relevant parts of the view:
#model PagedList.IPagedList<Dossier.Models.Item>
#using Dossier.Models.Item
...
<th>
#Html.DisplayNameFor(model => model.ItemName)
</th>
It seems IPagedList is not compatible with DisplayNameFor(). Any idea why this is happening, and how I could fix it? I know I could just manually enter the column names, but I'd like for that information to stay (and be changeable) in the model later.
You can try this
#Html.DisplayNameFor(model => model.FirstOrDefault().ItemName)
As an alternate solution to the accepted answer, remember that IPagedList inherits from IEnumerable. That means that you could write:
#model IEnumerable<Dossier.Models.Item>
At the beginning of the page, and just cast the model to IPagedList when needed:
#Html.PagedListPager((IPagedList)Model, page => Url.Action("Index", new { page = page }))
You can even declare the casted variable in the header, in order to use it multiple times within the page:
#{
ViewBag.Title = "My page title";
var pagedlist = (IPagedList)Model;
}
This would allow you to use the DisplayNameFor helper method, and access all PagedList methods/properties, without the need for dummy elements nor calling .FirstOrDefault() for each field.
I solved the problem by creating an overload of DisplayNameFor that accepts a IPagedList<TModel>.
namespace PagedList.Mvc
{
public static class Extensions
{
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString DisplayNameFor<TModel, TValue>(this HtmlHelper<IPagedList<TModel>> html, Expression<Func<TModel, TValue>> expression)
{
return DisplayNameForInternal(html, expression);
}
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "This is an extension method")]
internal static MvcHtmlString DisplayNameForInternal<TModel, TValue>(this HtmlHelper<IPagedList<TModel>> html, Expression<Func<TModel, TValue>> expression)
{
return DisplayNameHelper(ModelMetadata.FromLambdaExpression(expression, new ViewDataDictionary<TModel>()),
ExpressionHelper.GetExpressionText(expression));
}
internal static MvcHtmlString DisplayNameHelper(ModelMetadata metadata, string htmlFieldName)
{
string resolvedDisplayName = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
return new MvcHtmlString(HttpUtility.HtmlEncode(resolvedDisplayName));
}
}
}
I'll be sending a pull request to PageList project to include it into the project for everyone.
You do not need to change #Html.DisplayNameFor. Declare model in the view as:
#model IEnumerable<Dossier.Models.Item>
Just move your pager to partial view (lets name it "_Pager"):
#model IPagedList
...
#Html.PagedListPager(Model,
page => Url.Action("Index", new { page, pageSize = Model.PageSize }))
...
Render the pager in your view:
#Html.Partial("_Pager", Model)
Thats it.
P.S. You can create Html helper instead of partial view...
As an alternate solution you could try:
#Html.DisplayNameFor(x => x.GetEnumerator().Current.ItemName)
It will work even if the list is empty!

How to pass data to the view in mvc asp.net?

let me ask the question first.
Where is the correct place to call a function that load a list of values to be display on a view?
I create a controller like this
public ActionResult Create()
{
SeaModel newSea = new SeaModel();
return View("Season/CreateSea", newSea);
}
//I not quite sure if this should go here or in another place
partial class seaDataContext
{
public List<string> getSeaSettings()
{
var seaSettings = from p in settings
where p.setting == "periods"
select p.value;
return seaSettings.ToList<string>();
}
}
The model is like
public class SeaModel
{
[Required(ErrorMessage="*")]
[Display(Name = "Period Name")]
public string periods { get; set; }
}
Which create a view like
#using (Html.BeginForm()) {
#Html.ValidationSummary(true, "Please correct the following errors.")
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
#Html.LabelFor(model => model.periods)
</div>
<div class="editor-field">
#Html.Select(model => model.periods, ****My doubt comes here****)
#Html.ValidationMessageFor(model => model.periods)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
so, How and where do I pass the return of getSeaSettings() to the view?
Thanks
best practice is to make a Selectlist in your Model for this dropdown.
however you also can use the more easy option: using ViewData
public ActionResult Create()
{
SeaModel newSea = new SeaModel();
ViewData["myDropDown"] = new SelectList(listOfObjects, "valueOfTheObjectLikeID", "NameYouWantToShowInDropdown");
return View("Season/CreateSea", newSea);
}
then:
#Html.Select(model => model.periods, ViewData["myDropDown"] as SelectList)
dont forget in your [HttpPost] method to also fill in the viewdata if you'r validation fails, so the dropdown can be rebuilt.
You need to look at repository pattern. Have a look at this tutorial at asp.net site
http://www.asp.net/mvc/tutorials/creating-model-classes-with-linq-to-sql-cs
Stefanvds's approach was what I used to do.
But I found out there is a better way using additionalViewData.
Use this EditorFor HTML Helper extension method.
http://msdn.microsoft.com/en-us/library/ff406462.aspx
Instead of passing Select List Items into ViewData in the Controller, you do this in your View.
Pass in your list items as an anonymous object for the additionalViewData parameter.
Important thing is to use the same name as your Property Name.
#Html.EditorFor(
m => m.MyPropertyName,
new { MyPropertyName = Model.ListItemsForMyPropertyName }
);
Of course, you are passing in a View Model object.
public class MyViewModel
{
public int MyPropertyName;
public IList<SelectListItem> ListItemsForMyPropertyName;
}
EditorFor method uses your existing Editor View Templates.
So you don't need to specify CSS class names and HTML attributes again like when you use the Html.DropDown( ) method.
For example,
//------------------------------
// additionalViewData
//------------------------------
#Html.EditorFor(
m => m.MyPropertyName,
new { MyPropertyName = Model.ListItemsForMyPropertyName }
)
//------------------------------
// traditional approach requires to pass your own HTML attributes
//------------------------------
#Html.DropDown(
"MyPropertyName",
Model.ListItemsForMyPropertyName,
new Dictionary<string, object> {
{ "class", "myDropDownCssClass" }
}
);
//------------------------------
// DropDownListFor still requires you to pass in your own HTML attributes
//------------------------------
#Html.DropDownListFor(
m => m.MyPropertyName,
Model.ListItemsForMyPropertyName,
new Dictionary<string, object> {
{ "class", "myDropDownCssClass" }
}
);
That is why I like the additionalViewData approach more.
Because, the HTML code rendered relies on the Editor Templates completely.
Also, using specialized View Models make your code cleaner and easier to maintain.
Hope it helps.

ASP.Net MVC, ViewPage<Dynamic> and EditorFor/LabelFor

I'm playing with MVC3 using the Razer syntax, though I believe the problem to be more general.
In the controller, I have something like:
ViewModel.User = New User(); // The model I want to display/edit
ViewModel.SomeOtherProperty = someOtherValue; // Hense why need dynamic
Return View();
My View inherits from System.Web.Mvc.ViewPage
But if I try to do something like:
<p>
#Html.LabelFor(x => x.User.Name
#Html.EditorFor(x => x.User.Name
</p>
I get the error: "An expression tree may not contain a dynamic operation"
However, the use of ViewPage seems quite common, as are EditorFor/LabelFor. Therefore I'd be surprised if there's not a way to do this - appreciate any pointers.
Don't use ViewPage<Dynamic>. I would recommend you using a view model and strongly type your view to this view model:
var model = new MyViewModel
{
User = new User
{
Name = "foo"
},
SomeOtherProperty = "bar"
};
return View(model);
and then strongly type your view to ViewPage<MyViewModel> and:
#Html.LabelFor(x => x.User.Name)
#Html.EditorFor(x => x.User.Name)
<div>#Model.SomeOtherProperty</div>
It seems the expression trees => http://msdn.microsoft.com/en-us/library/bb397951.aspx must not contain any dynamic variables.
Unfortunately this is the case for TModel when you use dynamics in it.
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func> expression
)

Resources