Asp.net MVC dynamic Html attributes - c#

I have few elements on my view textboxes , dropdowns etc. All of them have some unique attributes created like that
<%: Html.DropDownListFor(model => model.MyModel.MyType, EnumHelper.GetSelectList< MyType >(),new { #class = "someclass", #someattrt = "someattrt"})%>
I would like to create a read only version of my page by setting another attribute disabled.
Does anybody know how can I do it using variable that can be set globally?
Something like:
If(pageReadOnly){
isReadOnlyAttr = #disabled = "disabled";
}else
{
isReadOnlyAttr =””
}
<%: Html.DropDownListFor(model => model.MyModel.MyType, EnumHelper.GetSelectList< MyType >(),new { #class = "someclass", #someattrt = "someattrt",isReadOnlyAttr})%>
I don’t want to use JavaScript to do that

I have done something similar to what you are after I think - basically I have a couple of different users of the system and one set have read-only privileges on the website. In order to do this I have a variable on each view model:
public bool Readonly { get; set; }
which is set in my model/business logic layer depending on their role privileges.
I then created an extension to the DropDownListFor Html Helper that accepts a boolean value indicating whether the drop-down list should be read only:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;
public static class DropDownListForHelper
{
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> dropdownItems, bool disabled)
{
object htmlAttributes = null;
if(disabled)
{
htmlAttributes = new {#disabled = "true"};
}
return htmlHelper.DropDownListFor<TModel, TProperty>(expression, dropdownItems, htmlAttributes);
}
}
Note that you can create other instances that take more parameters also.
Than in my view I simply imported the namespace for my html helper extension and then passed in the view model variable readonly to the DropDownListFor Html helper:
<%# Import Namespace="MvcApplication1.Helpers.HtmlHelpers" %>
<%= Html.DropDownListFor(model => model.MyDropDown, Model.MyDropDownSelectList, Model.Readonly)%>
I did the same for TextBoxFor, TextAreaFor and CheckBoxFor and they all seem to work well. Hope this helps.

Rather than disabling the drop down list, why not replace it with the selected option... if you are doing this for a lot of stuff, you should think about having a read-only view and an editable view...
<% if (Model.IsReadOnly) { %>
<%= Model.MyModel.MyType %>
<% } else { %>
<%= Html.DropDownListFor(model => model.MyModel.MyType, EnumHelper.GetSelectList< MyType >(),new { #class = "someclass", someattrt = "someattrt"})%>
<% } %>
And just as an aside, you only need to escape the attribute name with "#" if it is a reserved word, such as "class".
Update
Okay. I do have an answer for you - but on the condition that you read this stuff before you implement it.
MVC is all about separating the concerns. Putting logic in the controller that is specifically a concern of the view is an abuse of MVC. Please don't do it. Anything specific to the view, like HTML, attributes, layout - none of that should ever feature in "controllerville". The controller shouldn't have to change because you want to change something in the view.
It is really important that you understand what MVC is trying to achieve and that the following example breaks the whole pattern and puts view stuff in entirely the wrong place in your application.
The correct fix would be to have a "read" view and an "edit" view - or to put any conditional logic in the view. But here is a way of doing what you want. :(
Add this property to the Model.
public IDictionary<string, object> Attributes { get; set; }
In the controller you can conditionally set the attributes:
model.Attributes = new Dictionary<string, object>();
model.Attributes.Add(#"class", "test");
if (isDisabled) {
model.Attributes.Add("disabled", "true");
}
Use the attributes in your view:
<%= Html.TextBoxFor(model => model.SomeValue, Model.Attributes)%>

Related

When submitting MVC 5 view, data from partial view is missing [duplicate]

This question already has answers here:
getting the values from a nested complex object that is passed to a partial view
(4 answers)
Closed 6 years ago.
This question has been asked but none could solve my problem.
The problem is missing or null data from the partial view is not submittied (POST) along with the main view data.
I have a typed partial view called _Address.cshtml that I include in another view called Site.cshtml.
The typed site view binds to a view model called SiteEditModel.cs
public class SiteEditModel
{
...properties
public AddressEditModel Address {get;set;}
public SiteEditModel()
{
Address = new AddressEditModel();
}
}
The Site view has a form:
#model Insight.Pos.Web.Models.SiteEditModel
...
#using ( Html.BeginForm( "Edit", "Site", FormMethod.Post ) )
{
#Html.HiddenFor( m => m.SiteId )
...
#Html.Partial( "~/Views/Shared/Address.cshtml", this.Model.Address )
...
#Html.SaveChangesButton()
}
The partial Address view is just a bunch of #Html... calls that bind to the Address model.
#model Insight.Pos.Web.Models.AddressEditModel
#{
Layout = null;
}
<div>
#Html.HiddenFor(...)
#Html.HiddenFor(...)
#Html.HiddenFor(...)
#hmtl.LabelFor(...)
</div>
In the controller action Edit I can see the SiteEditModel is populated correctly, the Address property of that model is not.
Where do I go wrong?
Thank you so much.
http://davybrion.com/blog/2011/01/prefixing-input-elements-of-partial-views-with-asp-net-mvc/
#Html.Partial("~/Views/Shared/_Address.cshtml", Model.Address, new ViewDataDictionary
{
TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "Address" }
})
The key to fix this is with naming of the partialviews input-elements. The Render partial dont know it's a part of something bigger.
I've make an simple example on how you can fix this in a way that you can have multiple Addresses using the same partial view:
public static class HtmlHelperExtensions
{
public static MvcHtmlString PartialWithPrefix(this HtmlHelper html, string partialViewName, object model, string prefix)
{
var viewData = new ViewDataDictionary(html.ViewData)
{
TemplateInfo = new TemplateInfo
{
HtmlFieldPrefix = prefix
}
};
return html.Partial(partialViewName, model, viewData);
}
}
And use this extensions in the view like this:
#using (Html.BeginForm("Edit", "Site", FormMethod.Post))
{
#Html.HiddenFor(m => m.SiteId)
#Html.PartialWithPrefix("_Adress", this.Model.Address, "Address")
<input type="submit" />
}
You can of course make this a bit more fancy with expressions and reflection but that's another question ;-)
Your SiteEditModel Address property is not marked as public, change it to this instead:
public AddressEditModel Address {get;set;}
I would also change your partial to use SiteEditModel instead:
#model Insight.Pos.Web.Models.SiteEditModel
#{
Layout = null;
}
<div>
#Html.HiddenFor(m => m.Address.FooProperty)
...
</div>
This would mean that your properties would end up being named correctly in order for the model binder to pick them up.
Using the above example it would be Name"=Address.FooProperty".
As I remember correctly, the problem is that Html.Partial doesn't populate inputs names correctly. You should have something like:
<input id="Address.Street" name="Address.Street" />
but I assume you have following HTML:
<input id="Street" name="Street" />
You have few solutions:
Insert input name manually:
#Html.HiddenFor(x => x.Street, new { Name = "Address.Street" })
Use Html.EditorFor()
Override names resolving in Html.Partial()
The downside of first solution is that you are hardcoding property name, what isn't ideal. I'd recommend using Html.EditorFor() or Html.DisplayFor() helpers, cause they populate inputs names correctly.
Model binder could not bind child models correctly if you are populating them in partial view. Consider using editor templates instead which is implemented for this reason.
Put your AddressEditModel.cshtml file in \Views\Shared\EditorTemplates\ folder and in your main view use like this:
#using ( Html.BeginForm( "Edit", "Site", FormMethod.Post ) )
{
// because model type name same as template name MVC automatically picks our template
#Html.EditorFor(model=>model.Address)
// or if names do not match set template name explicitly
#Html.EditorFor(model=>model.Address,"NameOfTemplate")
}

How to concatenate the img src in asp.mvc aspx view page?

in asp.net mvc aspx page i am writing the html input image like this.
<img src="/public/images/"+<%: roleid %>+".jpg" alt="please load the image.." />
in above code roleid -1 is coming from db.but src not forming correctly.
expected result src : /public/images/1.jpg but src not forming correctly.please tell me how to concatenate the string in aspx view engine?
thnaks
Assuming roleid is defined somewhere above in the View, you should form the attribute value in this manner:
src='<%: string.Format("/public/images/{0}.jpg", roleid) %>'
Things to note:
The quotation pattern: single quotes for the whole value, double quotes for strings in C# code.
Value string is generated within the <%: %> tag
There are couple of things i would like talk about.
1) Generating Image URL at runtime in View is not a good practice. You should Use ViewModel for that purpose like this
public class MyViewModel
{
public MyModel m;
public void MyViewModel(MyModel m){this.m=m;}
public string ActualIMageUrl
{
get
{
return "public/Images/"+m.RoleId+".jpg";
}
}
}
Now make your view strongly typed view of this ViewModel class instead of Model.
2)Dont directly use IMG tag in your View, Rather create new method for HtmlHelperClass using extenion method as follows,
public static class MVCExtensions
{
public static MvcHtmlString Image(this HtmlHelper helper, string src, string altText, string height)
{
var builder = new TagBuilder("img");
builder.MergeAttribute("src", src);
builder.MergeAttribute("alt", altText);
builder.MergeAttribute("height", height);
return MvcHtmlString.Create(builder.ToString(TagRenderMode.SelfClosing));
}
}
Now in the View
#model MyViewModel
.
.
.
#Html.Image(Model.ActualImageUrl);
Hope it helped

Integer extension in mvc razor view

I am all new with ASP.NET MVC and Extension methods.
I have created two Extensions that i want to use in my View:
public static class Extensions
{
public static string ToYesNo(this bool value)
{
return value ? "Yes" : "No";
}
public static string MonthToString(this int value)
{
return (value >= 1 && value <= 12) ? CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(value) : "";
}
}
I can use ToYesNo with a bool in the View, but i cannot view MonthToString with an integer. I get:
'int' does not contain a definition for 'MonthToString'
The Extensions are in a namespace called BitvaerkAdmin.Models, and i reference that in th cshtml file.
Why can't i use my integer extension?
Edit:
I reference the extensions in my view like this:
#using BitvaerkAdmin.Models
<h3>
#ViewBag.Month.MonthToString()
</h3>
#foreach (Order order in ViewBag.Orders)
{
<td>
#order.Valid.ToYesNo()
</td>
}
OK, now that you have shown your code it is clear why it doesn't work. You use ViewBag (the root of all evil in ASP.NET MVC and the origin of all problems that people are having - little addition from the author of this answer).
Once you borrow its path the fall to the abyss is eminent. This fall will be accelerated by the cast that you need to perform in order to make it work:
#((int)(ViewBag.Month).MonthToString())
Simply try running the following console application and you will understand that dynamic variables cannot be used to dispatch extension methods:
public static class Extensions
{
public static string MonthToString(this int value)
{
return (value >= 1 && value <= 12) ? CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(value) : "";
}
}
class Program
{
static void Main()
{
dynamic foo = 123;
Console.WriteLine(foo.MonthToString()); // crash at runtime
}
}
See why I always critique ViewBag when I see people using it? Because it leads you to all kind of strange things. You lose Intellisense, you cannot dispatch extension methods on dynamic variables, ...
So actually you don't need to cast. You shouldn't use any ViewBag/ViewData at all. You should be using strongly typed view models:
#using BitvaerkAdmin.Models
#model MyViewModel
<h3>
#Model.Month.MonthToString()
</h3>
#foreach (Order order in Model.Orders)
{
<td>
#order.Valid.ToYesNo()
</td>
}
and to avoid the foreach loop you could use display templates:
#using BitvaerkAdmin.Models
#model MyViewModel
<h3>
#Model.Month.MonthToString()
</h3>
#Html.DisplayFor(x => x.Orders)
and then define a display template for the order which will automatically be rendered by the framework for all elements of the collection (~/Views/Shared/DisplayTemplates/Order.cshtml):
#using BitvaerkAdmin.Models
#model Order
<td>
#Model.Valid.ToYesNo()
</td>
Everything is now strongly typed and working.
After giving reference of Extention class in My view I tried as below and it worked for me.
#using NameSpace Of your Extentions class;
#{
int i = 10;
}
<span>#i.MonthToString()</span>

Why does a model accessor with the name "State" cause the model to be posted as null?

I was having a weird issue with a very simple model. When posted back to the controller, the model was always null. Not being able to find the issue, I pulled it apart rebuilt the model, adding an accessor at a time.
I finally discovered that having a string accessor called "State" and using it in a view was causing the issue:
<%= Html.HiddenFor(m => m.State) %>
Why would this happen?
Here is the model:
public class StudentSelectState
{
public string State { get; set; }
public int SelectedYear { get; set; }
public IDictionary<string, string> Years { get; set; }
}
Here is the controller:
[HttpGet]
public ActionResult SelectStudent()
{
var StudentYears = absenceServices.GetStudentYears();
var state = new StudentSelectState {Years = Lists.StudentYearListToDictionary(StudentYears)};
return View(state);
}
[HttpPost]
public ActionResult SelectStudent(StudentSelectState state)
{
var StudentYears = absenceServices.GetStudentYears();
state.Years = Lists.StudentYearListToDictionary(StudentYears);
return View(state);
}
and here is the view:
<% using (Html.BeginForm())
{%>
<%= Html.ValidationSummary() %>
<%= Html.TextBoxFor(m => m.State) %>
<%= Html.RadioButtonListFor(m => m.SelectedYear, Model.Years, "StudentYears") %>
<div style="clear: both;">
<input value="submit" />
</div>
<% } %>
The RadioButtonListFor is a HtmlHelper I wrote to populate RadioButtonLists.
I am using Ninject 2.0 to inject services into the contructor, but I don't think this has a bearing on this issue.
I could rename the accessor, but I'm curious as to why this is happening.
You could also rename the argument of your POST action.
[HttpPost]
public ActionResult SelectStudent(StudentSelectState model)
When you POST the form the following is sent in the request:
State=abcd
Now the default model binder sees that your action argument is called state and it tries to bind the abcd value to it which obviously fails because the state variable is not a string. So be careful when naming your view model properties.
To avoid those kind of conflicts I prefer to name my action arguments model or viewModel.
Yet another possibility if you don't want to rename anything is to use the [BindPrefix] attribute, like so:
[HttpPost]
public ActionResult SelectStudent([Bind(Prefix="")]StudentSelectState state)
When StudentSelectState is posted back to the controller the default mode binder (because you are not using a IModelBinder) can not know when to put in the StudentSelectState instance.
The view will not hold the state for the State property and it has to be specified in the form or obtained from a different method to be returned to the controller action.
You could use a hidden field for this or bind it using a custom IModelBinder class.
Hope this helps.

Using ASP.NET MVC, how to best avoid writing both the Add View and Edit View?

The Add view and the Edit view are often incredibly similar that it is unwarranted to write 2 views. As the app evolves you would be making the same changes to both.
However, there are usually subtle differences. For instance, a field might be read-only once it's been added, and if that field is a DropDownList you no longer need that List in the ViewData.
So, should I create a view data class which contains all the information for both views, where, depending on the operation you're performing, certain properties will be null?
Should I include the operation in the view data as an enum?
Should I surround all the subtle differences with <% if( ViewData.Model.Op == Ops.Editing ) { %> ?
Or is there a better way?
It's pretty easy really. Let's assume you're editing a blog post.
Here's your 2 actions for new/edit:
public class BlogController : Controller
{
public ActionResult New()
{
var post = new Post();
return View("Edit", post);
}
public ActionResult Edit(int id)
{
var post = _repository.Get(id);
return View(post);
}
....
}
And here's the view:
<% using(Html.Form("save")) { %>
<%= Html.Hidden("Id") %>
<label for="Title">Title</label>
<%= Html.TextBox("Title") %>
<label for="Body">Body</label>
<%= Html.TextArea("Body") %>
<%= Html.Submit("Submit") %>
<% } %>
And here's the Save action that the view submits to:
public ActionResult Save(int id, string title, string body)
{
var post = id == 0 ? new Post() : _repository.Get(id);
post.Title = title;
post.Body = body;
_repository.Save(post);
return RedirectToAction("list");
}
I don't like the Views to become too complex, and so far I have tended to have separate views for Edit and Add. I use a user control to store the common elements to avoid repetition. Both of the views will be centered around the same ViewData, and I have a marker on my data to say whether the object is new or an existing object.
This isn't any more elegant than what you have stipulated, so I wonder if any of the Django or Rails guys can provide any input.
I love asp.net mvc but it is still maturing, and still needs more sugar adding to take away some of the friction of creating websites.
I personally just prefer to use the if/else right there in the view. It helps me see everything going on in view at once.
If you want to avoid the tag soup though, I would suggest creating a helper method.
<%= Helper.ProfessionField() %>
string ProfessionField()
{
if(IsNewItem) { return /* some drop down code */ }
else { return "<p>" + _profession+ "</p>"; }
}
You can specify a CustomViewData class and pass the parameters here.
public class MyViewData {
public bool IsReadOnly { get; set; }
public ModelObject MyObject { get; set; }
}
And both views should implement this ViewData.
As a result you can use provided IsReadOnly property to manage the UserControl result.
As the controller uses this, you can unit test it and your views doesn't have implementation, so you can respect the MVC principles.

Categories

Resources