#{IEnumerable<BC.Models.APPLICATION> data = ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No").Filterable(true);
})
But i want to do something like that :
#if(some conditon)
{
#{IEnumerable<BC.Models.APPLICATION> data = ViewBag.list;}
}
else
{
#{IEnumerable<BC.Models.RIGHTS> data = ViewBag.list;}
}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
But its not working can anybody have some idea about it.
Now
if i do something like this it works
#if(some conditon)
{
#{IEnumerable<BC.Models.APPLICATION> data = ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
}
else
{
#{IEnumerable<BC.Models.RIGHTS> data = ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
}
My problem is that APPLICATION_NO property present in both Model class so i don`t want to use
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
Twice in my code.
Your problem is that you are not using when of the most important concepts in MVC: view models.
As for your last comment, what you want to use is a view model, i.e. a class created specifically to send data to the view to show it.
To do so:
create a public class which has the APPLICATION_NO (needed for the view model class)
create another public class which will be your view model. That's a class that shapes the data as you need it on the razor template (in this case, it will hold a list of the class defined in 1)
in your controller, return the view passing the model as second parameter. I.e. don't use the ViewBag/ViewData, but a view model instead, like this return View("ViewName", model)
use the model in your view: declare the model type using #model and use it inside the Razor templae with the provided Model variable
In this way, you shape the data on the server, and avoid including a lot of code in you Razor template (which is not advisable). And, of course, you have intellisense, and your templates become typed.
Code for 1:
public class ApplicationModel
{
public int APPLICATION_NO {get; set;}
}
Code for 2:
public class ApplicationsViewModel
{
public List<ApplicationModel> Applications { get; set; }
}
Code for 3 (inside the controller)
var model = new ApplicationsViewModel();
if (...) // include the condition to choose the kind of data inside the list
{
model.Applications = list.Select(item =>
new ApplicationModel { APPLICATION_NO = item.APPLICATION_NO } ).ToList()
}
else
{
// same code here, for the other kind of data in the list
}
// return the view with this model
return View("ApplicationView", model);
Code for 4:
// decalre the model type at the beginning
#model YourNamespace.ApplicationViewModel;
// access the model using the Model variable
#Html.Grid(Model.Applications).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
This allows you to build MVC applications with several advantages, for example testability, code reusing, readability or availability of a ModelState. Belive me, many of theses things are really,really important, specially the ModelState.
Besides you can use code annotations (attributes that give extra info to the HTML helpers) in your view model, that attributes can provide labels, validation and some other automatic functionality.
Don't doubt to include all the needed properties inside your view model.
Using a view model allows you to create an ad-hoc class without the need to change you domain or business layer classes, i.e. you don't need to use interfaces, code annotations and so on. However many times it's interesting to add the code annotations in the business classes and nest them inside the view models.
Remember tha sometimes you can use the same view model to post the data back to the server, specifying it as the paramter type of your POST action.
By the way, the interface solution is a good one, but this solution doesn't required the interface. (This solution would have a better implementationusing that interface, but that's your choice).
#if(some conditon)
{
#{IEnumerable<BC.Models.APPLICATION> data = ViewBag.list;}
}
else
{
#{IEnumerable<BC.Models.RIGHTS> data = ViewBag.list;}
}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
The code can't work because data is declared into if blocks.
If the grid has to work only on shared fields of the two classes you can think about using an Interface that APPLICATION and RIGHTS will implement and change the code like this:
#{IEnumerable<BC.Models.IAPPLICATION_NO> data = (IEnumerable<BC.Models.IAPPLICATION_NO>)ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
where IAPPLICATION_NO is an interface like:
public interface IAPPLICATION_NO
{
string APPLICATION_NO { get; }
}
I don't know what APPLICATION_NO is, so I used string and the interface can define only get for grid.
Otherwise, if you need to display different data for those two types you should consider using two views or different grid declaration in the if blocks.
I worked on a sample of my answer on VS:
I attach you the code:
public interface AInterface
{
string AProperty { get; }
}
public class AList : AInterface
{
public string AProperty { get; set; }
}
public class BList : AInterface
{
public string AProperty { get; set; }
}
these are the classes
now the controller:
public class TestController : Controller
{
public ActionResult Index()
{
var random = new Random((int)DateTime.Now.Ticks);
if (random.Next() % 2 == 0)
ViewBag.List = new List<AList> { new AList { AProperty = "Atestvalue" } };
else
ViewBag.List = new List<BList> { new BList { AProperty = "Atestvalue" } };
return View();
}
}
and the view:
#{
ViewBag.Title = "Index";
IEnumerable<TestMvcApplication.Interfaces.AInterface> test = ViewBag.List;
}
<h2>Index</h2>
#foreach (var item in test)
{
<div>
#item.AProperty
</div>
}
this solves your problem as you can see
Without using interfaces:
#{IEnumerable<dynamic> data = (IEnumerable<dynamic>)ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
but you lose the IntelliSense completion and if the member is missing I think you receive a runtime error.
I tried your Grid assembly but it uses c# Expression and it's incompatible with dynamic.
Another solution could be casting one list to another using LINQ in the controller:
IEnumerable<BC.Models.Application> data;
if (some condition)
{
data = applicationList; //applicationList's type is IEnumerable<BC.Models.Application>
}
else
{
data = rightsList.Select(t => new Application { APPLICATION_NO = t.APPLICATION_NO }); //rightsList's type is IEnumerable<BC.Models.RIGHTS>
}
ViewBag.list = data;
In the view you can keep the working code you posted at the top of the question. You have not multitype IEnumerable support because you use only one type but without using a common interface between these classes I think we must go to reflection but I think it's hard to write that code.
Why not
#{string columnName = "default column name";
if(some_condition)
{
// I'm not sure what's going on with the data variable
columnName = "alternate column name";
}
else
{
// Again, do your stuff with the data variable
}
}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled(columnName);
})
I think that you're going to end up with (at best) confusing code if you try to be too clever in your views. A grid for rendering IEnumerable<A> isn't adaptable to rendering IEnumerable<B> unless A:ISomeInterface and B:ISomeInterface and the grid renders ISomeInterface. Alternatively just pass the column name as a property of the view model.
Related
<div class="form-gridcontrol">
<label>Notes</label>
#Html.CustomTextArea(m => m.Notes)
</div>
In ASP.NET MVC , I have created a custom textarea and inputing/displaying data from the database using a Model.Above is the code where you can see the Notes are getting assigned to #Html.CustomTextArea.
I have a situation where , I need to display a text "Not Applicable" if there is no value in "m.Notes"
How I should right the logic in the above code? Please guide.
There are multiple possible ways for this. One of the way is that you can populate in the controller action from where it is loaded like:
public ActionResult YourActionMethod()
{
............
............
if(String.IsNullOrEmpty(model.Notes))
model.Notes = "Not Applicable";
return View(model);
}
Another way can be to introdcue the backing field on your property and write in it's getter:
private String _notes;
public String Notes
{
get
{
return String.IsNullOrEmpty(_notes) ? "Not Applicable" : _notes;
}
set
{
_notes = value;
}
}
You can try this:
#if (Model.Notes != null)
{
#Html.CustomTextArea(m => m.Notes)
}
else
{
#Html.CustomTextArea( m => m.Notes, new { #Value = "Not Applicable"})
}
edit:this is not working with textarea
else
{
#Html.CustomTextArea(m => m.Notes, new {id="mytextarea"})
<script>
$("#mytextarea").text("Not Applicable")
</script>
}
I got a trick for you :)
I am trying to populate a Kendo mvcGrid and I keep getting the error An item with the same key has already been added.
I have seen elsewhere that duplicate model properties is the likeliest source of the problem but I have no duplicate properties in my model. I've even simplified everything to just a basic mock up to eliminate as many possible variables as possible..and I cant get the grid to load data.
I've tried it as a Html.Partial where its not loaded initially because of a empty model and then loading it through jquery...Ive tried with passing in am empty model so that the grid will load initially without data and then doing a datasource refresh...no luck there either. Basically as soon as I try to add content to the grid with a populated model I get the error and no data is loaded.
Been fighting this issue for 2 days now and had no luck. Ill post my code here maybe someone else can see what I'm missing.
One variable that cant change is the grid resides on a partial view with a model different from its parent view.
in the parent view I have tried both
<div id ="columnkgrid">#{Html.RenderPartial("Columns", new TestFieldIssue.Models.FieldListModel());}</div>
and
<div id="columnkgrid">#Html.Partial("Columns", new TestFieldIssue.Models.FieldListModel())</div>
either way will succeed in posting a grid with no data..it has no data yet because its populated by the selection of a value in a dropdown list.
so as to not over complicate the sample I've just set a hard coded value in the jquery function I have been using to refresh the grid.
function loadgrid() {
var grid = $("#grid").data("kendoGrid");
var val = 1;
grid.dataSource.read({ table_pk: val });
grid.refresh();
}
the grid code in the partial once again kept it simple without any bells and whistles just to test.
#(Html.Kendo().Grid(Model.fields)
.DataSource(data => data
.Ajax()
.Model(model => model.Id(m => m.field_pk))
.Read(r => r.Action("Columns","Home"))
)
.Name("grid")
.Columns(c =>
{
c.Bound(m => m.field_name).Title("Field Name").Width(100);
})
)
and the controller methods loading some mock data
public ActionResult Columns(int? table_pk)
{
FieldListModel model;
if (table_pk == null)
{
model = GetEmptyColumns();
}
else
{
model = GetColumns();
}
return PartialView("Columns", model);
}
public FieldListModel GetColumns()
{
FieldListModel model = new FieldListModel();
model.fields = new List<FieldModel>();
model.fields.Add(new FieldModel { field_name = "field1", field_pk = 1 });
model.fields.Add(new FieldModel { field_name = "field2", field_pk = 2 });
model.fields.Add(new FieldModel { field_name = "field3", field_pk = 3 });
return model;
}
public FieldListModel GetEmptyColumns()
{
FieldListModel model = new FieldListModel();
model.fields = new List<FieldModel>();
model.fields.Add(new FieldModel { field_name = "", field_pk = 0 });
return model;
}
and a very simple Model
public class FieldListModel
{
public List<FieldModel> fields { get; set; }
}
public class FieldModel
{
public int field_pk { get; set; }
public string field_name { get; set; }
}
I made few changes to run your code (correct version of Kendo and JQuery). May be those related to setup at my machine. I was able to reproduce the problem.
Then I changed the action code and was able to see the values populated in Grid:
public ActionResult Columns(int? table_pk, [DataSourceRequest] DataSourceRequest request)
{
FieldListModel model;
if (table_pk == null)
{
model = GetEmptyColumns();
}
else
{
model = GetColumns();
}
return Json(model.fields.ToDataSourceResult(request));
}
The change is accepting an additional parameter in action method of type DataSourceRequest. The Kendo grid wraps request in this object to specify sorting and paging information. The grid itself gets updated with data wrapped under DataSourceRequest object (note in return statement). More information here.
So I have a controller like this:
public class TestController : Controller
{
//
// GET: /Test/
public ActionResult Index()
{
return View("Test");
}
public ActionResult Post(IList<Test> LanguageStrings, IList<Test> LanguageStringsGroup, IList<string> Deleted, IList<string> DeletedGroup)
{
if (LanguageStrings == null)
{
throw new ApplicationException("NULL");
}
return View("Test");
}
}
public class Test
{
public string Val { get; set; }
public string Another { get; set; }
}
And a view like this:
<h2>Test</h2>
#using (Html.BeginForm("Post", "Test"))
{
#Html.Hidden("LanguageStrings[0].Val", "test1")
#Html.Hidden("LanguageStrings[0].Another")
#Html.Hidden("LanguageStrings[1].Val", "test2")
#Html.Hidden("LanguageStrings[1].Another")
#Html.Hidden("LanguageStringsGroup[0].Val", "test4")
#Html.Hidden("Deleted[0]")
#Html.Hidden("Deleted[1]")
#Html.Hidden("Deleted[2]")
#Html.Hidden("DeletedGroup[0]")
<button>Post</button>
}
When I post the form my controller throws the exception because LanguageStrings is null. The strange part I mentioned in the title is that if I add one more record to the list everything works.
Like this:
<h2>Test</h2>
#using (Html.BeginForm("Post", "Test"))
{
#Html.Hidden("LanguageStrings[0].Val", "test1")
#Html.Hidden("LanguageStrings[0].Another")
#Html.Hidden("LanguageStrings[1].Val", "test2")
#Html.Hidden("LanguageStrings[1].Another")
#Html.Hidden("LanguageStrings[2].Val", "test3")
#Html.Hidden("LanguageStrings[2].Another")
#Html.Hidden("LanguageStringsGroup[0].Val", "test4")
#Html.Hidden("Deleted[0]")
#Html.Hidden("Deleted[1]")
#Html.Hidden("Deleted[2]")
#Html.Hidden("DeletedGroup[0]")
<button>Post</button>
}
It also works when I remove the "Deleted" list.
Like this:
<h2>Test</h2>
#using (Html.BeginForm("Post", "Test"))
{
#Html.Hidden("LanguageStrings[0].Val", "test1")
#Html.Hidden("LanguageStrings[0].Another")
#Html.Hidden("LanguageStrings[1].Val", "test2")
#Html.Hidden("LanguageStrings[1].Another")
#Html.Hidden("LanguageStringsGroup[0].Val", "test4")
#Html.Hidden("DeletedGroup[0]")
<button>Post</button>
}
This has something to do with the naming I am using. I have already solved the problem with renaming LanguageStrings to something else. But I would like to understand what is happening here because probably I could learn something from it how MVC maps request body and will be able to avoid similar time consuming problems.
Please help me and explain the cause of this.
You found a bug in the PrefixContainer of MVC 4 which has already been fixed in MVC 5.
Here is the fixed version with comments about the bug:
internal bool ContainsPrefix(string prefix)
{
if (prefix == null)
{
throw new ArgumentNullException("prefix");
}
if (prefix.Length == 0)
{
return _sortedValues.Length > 0; // only match empty string when we have some value
}
PrefixComparer prefixComparer = new PrefixComparer(prefix);
bool containsPrefix = Array.BinarySearch(_sortedValues, prefix, prefixComparer) > -1;
if (!containsPrefix)
{
// If there's something in the search boundary that starts with the same name
// as the collection prefix that we're trying to find, the binary search would actually fail.
// For example, let's say we have foo.a, foo.bE and foo.b[0]. Calling Array.BinarySearch
// will fail to find foo.b because it will land on foo.bE, then look at foo.a and finally
// failing to find the prefix which is actually present in the container (foo.b[0]).
// Here we're doing another pass looking specifically for collection prefix.
containsPrefix = Array.BinarySearch(_sortedValues, prefix + "[", prefixComparer) > -1;
}
return containsPrefix;
}
I have had much more success with #Html.HiddenFor() for posting back to the controller. Code would look something like this:
#for (int i = 0; i < #Model.LanguageStrings.Count; i++)
{
#Html.HiddenFor(model => model.LanguageStrings[i].Val, string.Format("test{0}", i))
#Html.HiddenFor(model => model.LanguageStrings[i].Another)
}
Most HTML helper methods have a "For" helper that is intended to be used for binding data to models. Here is another post on the site that explains the "For" methods well: What is the difference between Html.Hidden and Html.HiddenFor
I have ASP.NET MVC 4 application with one view model class and about 20 views representing this view model. This views differs only by fields which user can edit. I want to merge all that views to one and define list of properties available to editing in strongly-typed manner. Ideally, I want something like this:
// Action
public ActionResult EditAsEngineer(int id)
{
//...
viewModel.PropertiesToChange = new List<???>()
{
v => v.LotNumber,
v => v.ShippingDate,
v => v.Commentary
};
return View(viewModel);
}
// View
if (#Model.PropertiesToChange.Contains(v => v.LotNumber)
{
#Html.TextBoxFor(m => m.LotNumber)
}
else
{
#Model.LotNumber
}
Is it possible to do something like this? Or is there a better solution?
Thank you.
Why note something like this (its pseudo code)
public class Prop{
string PropertyName {get;set;}
bool PropertyEditable {get;set;}
}
public ActionResult EditAsEngineer(int id)
{
viewModel.PropertiesToChange = new List<Prop>()
{
new Prop{PropertyName = LotNumber, PropertyEditable = true}
};
return View(viewModel);
}
#foreach (var pin Model.PropertiesToChange)
{
if(p.PropertyEditable){
#Html.TextBoxFor(p)
}else{
#Html.DisplayFor(p)
}
}
This will solve HALF of your problem. You will also need to create a IEqualityComparer<Expression> for your code to work (the default is to check for ref-equals).
return from p in typeof(T).GetProperties()
let param = System.Linq.Expressions.Expression.Parameter(typeof(T), "x")
let propExp = System.Linq.Expressions.Expression.Property(param, p)
let cast = System.Linq.Expressions.Expression.Convert(propExp, typeof(object))
let displayAttribute = p.CustomAttributes.OfType<System.ComponentModel.DataAnnotations.DisplayAttribute>()
.Select(x => x.Order).DefaultIfEmpty(int.MaxValue).FirstOrDefault()
orderby displayAttribute
select System.Linq.Expressions.Expression.Lambda<Func<T, object>>(cast, new [] {param});
This will list out ALL the properties for T. You would also probabily want to use Expression<Func<T, object>> as the type for defining your list of properties.
This will allow you to create a generic view over all properties.
Also you will want to wrap this in some kind of a cache, as this code is SLOW.
I access data with:
public ActionResult Index()
{
//IEnumerable<ChatLogs> c = from p in db.ChatLogs select p;
//return View(c);
using (var db = new ChatLogContext())
{
var list = db.ChatLogs.ToList();
return View(list);
}
}
I would like to know how to save this collection of data inside of TextArea in View? When we used webforms we could just textBox.Text = textBox.Text + "some data from database";
View:
#model IEnumerable<Chat.Models.ChatLogs>
#Html.TextArea("chatScreen", new { #Class = "chatScreen" })
Thank you.
I'd suggest that you create a view model. For example:
class ChatLogsViewModel
{
public string LogListString { get; set; }
}
Pass that to the view, instead of passing the raw list:
var list = db.ChatLogs.ToList();
var vm = new ChatLogsViewModel { LogListString = /* convert list to single string here */ };
return View(vm);
And in the view, just do something like this:
#model Your.Namespace.ChatLogsViewModel
#Html.TextAreaFor(model => model.LogListString)
Using view models will make your life easier as soon as you decide that you want to pass more information to the view than what a single domain model can carry.
In you .cshtml view, you can access data using #Model
Now, since you have a list, I'd recommend you join it and then assign it to TextArea like
#{var strList = string.Join(" ", Model)}
#Html.TextArea("myTextArea",strList)