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>
Related
I know it would be a basic question but I'm a newbie to ASP.Net MVC. I have fetched data from database using LINQ but there is an issue. I wanna bind that data with input fields of a customized webform. (I'm using MVC). I wanna populate the input fields of webform with fetched data. I'm using EF Database first approach.
My Controller and view is attached.
Controller ActionMethod
public class HomeController : Controller
{
public ActionResult Index()
{
AutoRTGSEntities_1 dc = new AutoRTGSEntities_1();
//dc.policies.Where(cb => cb.Section_Key.Contains("SenderBIC"));
return View(dc.policies.Where(cb => cb.Policy_Section.Contains("RTGS")).ToList()); //get RTGS policy section data
}
}
View
#model IEnumerable<Swift_MT_103.policy>
#{
ViewBag.Title = "Home Page";
}
<div> #Model{ #Html.TextBoxFor(x => x.data_Value)) } </div>
<div> <input type="text" name="ReceiverBIC" id="ReceiverBIC" /> </div>
Rest is HTML and CSS. Snap is attached.
Here's a very basic example of how to this. Let's say you have following class:
public class User
{
public int Id { get; set; }
[Display(Name = "Name")]
public string Name { get; set; }
[Display(Name = "E-mailaddress")]
public string E-mail { get; set; }
}
In the controller you get the user:
public ActionResult Index(int id)
{
var user = Db.Users.FirstOrDefault(x => x.Id == id);
if(user != null)
{
return View(user);
}
//Return to the 'Error' view as no user was found
return View("Error");
}
You also need a View to show everything on screen. Make it a strongly typed view, this way you can pass a Model to it. This class will hold all data you want to pass to the view. Code of the view:
//This line lets the view know which class represents the model
#model User
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
Using the Razor syntax instead of plain HTML it is very easy to construct and bind your form elements to the corresponding data. In this case the label will show the value of the Display attribute in the User class and the values of the user will be filled in the textboxes.
More reading:
Getting started with ASP.NET MVC 5
ASP.NET MVC Overview
Update:
In case you have a list of objects, you need to enumerate them in the view:
#model IEnumerable<string>
#foreach (var value in Model)
{
<div>#value</div>
}
And if the model is a class and has a property that is a list:
//Let's say a user has lots of names
public class User
{
public int Id { get; set; }
public List<string> Names { get; set; }
}
//View:
#model User
#Html.TextBoxFor(m => m.Id)
#foreach (var name in Model.Names)
{
<div>#name</div>
}
Try to implement a correct ASP.NET MVC architecture. To get this completed, you'll need to use proper Razor (.cshtml type) Syntax in your Views. Best practice:
Create a dedicated ViewModel class in the Model directory. You might call it CustomerCreditTransferViewModel for example. It should contain all Properties you want to display/edit anywhere on the page.
Once you selected your data from your DBContext in your Action, create an instance of CustomerCreditTransferViewModel and populate all fields from the result.
Update your View to use #model CustomerCreditTransferViewModel instead of Swift_MT_103.policy (believe me, this is going to make your live much easier in future)
Copy-paste your raw HTML Code into the page and start looking for all Fields you want to bind, e.g. Text fields (<input type="text" name="accountno" value="" />) and replace them with the Razor Syntax for Data Binding (#Html.TextBoxFor(x => x.AccountNo)). If done correctly, they should be populated now.
Next step is probably the POST. Follow the base MVC Post technique from the Tutorials. Ensure that the Posted Value is of type CustomerCreditTransferViewModel) again, so you can easily validate values and map back to type of Swift_MT_103.policy.
This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 6 years ago.
Controller:
[HttpPost]
public ActionResult GetChecked(FormCollection formCollection)
{
var checked = formCollection["Checked"].Split(',');
var ids = formCollection["Original.ID"].Split(',');
}
View:
#model IEnumerable<Models.Entry> []
<table>
#using (Html.BeginForm("GetChecked", "ControllerName"))
{
#foreach (var item in Model[0])
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Original.ID)
#Html.HiddenFor(modelItem => item.Original.ID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Original.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.New.Name)
</td>
<td>
#Html.CheckBoxFor(modelItem => item.Checked)
</td>
</tr>
}
</table>
//Then there's another table with Model[1]
Model
public class Entry
{
public Entry()
{
Original = new SomeObject();
New = new SomeObject();
Checked = false;
}
public SomeObject Original { get; set; }
public SomeObject New { get; set; }
public bool Checked { get; set; }
}
This works but the ids-array in my controller gets both a true- and a false value for the checked rows. I read that it's because of the FormCollection.
Question: How can I let GetChecked take an IEnumerable<Models.Entry> as a parameter instead? When I tried it it resulted in a null value.
There are a couple of things that you should change:
When rendering controls from a list or array using CheckBoxFor, EditorFor, etc. you should never use foreach - instead, ALWAYS use a for-loop and apply indexing to your collection. The reason is that indexing creates numbered items in your <form> that then no longer conflict with each other, and those numbered items are precisely what you need to successfully process a list of submitted values. See this answer for a simple example: https://stackoverflow.com/a/15375949/1220550
Don't use FormCollection, use a ViewModel class instead. By using FormCollection you are giving up on Databinding / ModelState / ValidationSummary which together are a superb feature of ASP.NET MVC. It is too much to explain it all here, but here is a great link that does exactly that.
It is best to use a fully defined ViewModel class, not only for databinding (see before) but also for consistency and ease-of-use. Having an array of IEnumerable<X> as your #model is at best confusing, and at worst a cause for errors. And what if suddenly you also want to pass an int? Impossible with IEnumerable<X>[], yet a piece of cake with a ViewModel - just add it to the class.
The result is null because you should bind your IEnumerable interface with a model binder. I think you are looking to create a model binding provider, because a provider can look at the type of the property and then create your custom model binder just for that property.
Take a look on this link also http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx
I want to create a view using razor template, but I do not want to write a class for model, because in many views i will have many queries which will be returning diferent models.
For example I have a linq query:
from p in db.Articles.Where(p => p.user_id == 2)
select new
{
p.article_id,
p.title,
p.date,
p.category,
/* Additional parameters which arent in Article model */
};
I need to write a View for this query. This query returns a Articles.
Now I dont know how should looks like a model definition.
I tried to use this deffinition:
#model System.Collections.IEnumerable
But then I had an erros than fileds doesnt exists in object type:
*CS1061: 'object' does not contain a definition for 'addition_field' and no extension method 'addition_field' accepting a first argument of type 'object' could be found*
This is my model for which I do not want to write a next model. Of course
The short answer is that using anonymous types is not supported, however, there is a workaround, you can use an ExpandoObject
Set your model to
#model IEnumerable<dynamic>
Then in the controller
from p in db.Articles.Where(p => p.user_id == 2)
select new
{
p.article_id,
p.title,
p.date,
p.category,
/* Additional parameters which arent in Article model */
}.ToExpando();
...
public static class Extensions
{
public static ExpandoObject ToExpando(this object anonymousObject)
{
IDictionary<string, object> anonymousDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(anonymousObject);
IDictionary<string, object> expando = new ExpandoObject();
foreach (var item in anonymousDictionary)
expando.Add(item);
return (ExpandoObject)expando;
}
}
The simplest solution if you are using C# 7.0+ (introduced in Visual Studio 2017+) is to use a tuple rather than an anonymous type.
Razor View: "_MyTupledView.cshtml"
#model (int Id, string Message)
<p>Id: #Model.Id</p>
<p>Id: #Model.Message</p>
Then when you bind this view, you just send a tuple:
var id = 123;
var message = "Tuples are great!";
return View("_MyTupledView", (id, message))
I think this is an even better solution:
http://buildstarted.com/2010/11/09/razor-without-mvc-part-iii-support-for-nested-anonymous-types/
This allows for nested anonymous types, which the aforementioned expando-object solution won't handle.
It seems you can't pass anonymous types but if you just want the values of the type you might pass an enumerable of an object array to view.
View:
#model IEnumerable<object[]>
#{
ViewBag.Title = "Home Page";
}
<div>
<table>
#foreach (var item in Model)
{
<tr>
<td>#item[0].ToString()</td>
<td>#item[1].ToString()</td>
</tr>
}
</table>
</div>
Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
namespace ZZZZZ
{
public class HomeController : Controller
{
public ActionResult Index()
{
List<object[]> list = new List<object[]> { new object[] { "test1", DateTime.Now, -12.3 } };
return View(list);
}
}
}
How do I define a method in Razor?
Leaving alone any debates over when (if ever) it should be done, #functions is how you do it.
#functions {
// Add code here.
}
You mean inline helper?
#helper SayHello(string name)
{
<div>Hello #name</div>
}
#SayHello("John")
It's very simple to define a function inside razor.
#functions {
public static HtmlString OrderedList(IEnumerable<string> items)
{ }
}
So you can call a the function anywhere. Like
#Functions.OrderedList(new[] { "Blue", "Red", "Green" })
However, this same work can be done through helper too. As an example
#helper OrderedList(IEnumerable<string> items){
<ol>
#foreach(var item in items){
<li>#item</li>
}
</ol>
}
So what is the difference?? According to this previous post both #helpers and #functions do share one thing in common - they make code reuse a possibility within Web Pages. They also share another thing in common - they look the same at first glance, which is what might cause a bit of confusion about their roles. However, they are not the same. In essence, a helper is a reusable snippet of Razor sytnax exposed as a method, and is intended for rendering HTML to the browser, whereas a function is static utility method that can be called from anywhere within your Web Pages application. The return type for a helper is always HelperResult, whereas the return type for a function is whatever you want it to be.
You could also do it with a Func like this
#{
var getStyle = new Func<int, int, string>((width, margin) => string.Format("width: {0}px; margin: {1}px;", width, margin));
}
<div style="#getStyle(50, 2)"></div>
Razor is just a templating engine.
You should create a regular class.
If you want to make a method inside of a Razor page, put them in an #functions block.
You can also just use the #{ } block to create functions:
#{
async Task<string> MyAsyncString(string input)
{
return Task.FromResult(input);
}
}
Then later in your razor page:
<div>#(await MyAsyncString("weee").ConfigureAwait(false))</div>
Here is how the list helper is written in ASP.NET Core 3
You can now include HTML markup in the body of a method declared in a code block as a local method as previously, or in an #functions block. The method should return
void, or Task if it requires asynchronous processing :
#{
void Template(string[] listItems, string style)
{
<ul>
#foreach (var listItem in listItems)
{
<li class="#style">#listItem</li>
}
</ul>
}
}
MyModelVm.cs
public class MyModelVm
{
public HttpStatusCode StatusCode { get; set; }
}
Index.cshtml
#model MyNamespace.MyModelVm
#functions
{
string GetErrorMessage()
{
var isNotFound = Model.StatusCode == HttpStatusCode.NotFound;
string errorMessage;
if (isNotFound)
{
errorMessage = Resources.NotFoundMessage;
}
else
{
errorMessage = Resources.GeneralErrorMessage
}
return errorMessage;
}
}
<div>
#GetErrorMessage()
</div>
You could also use the code block below. It is much cleaner and has more functionality. You can also insert variables above and functions below. Instead of using 2 seperate code blocks.
#{
string exampleVariable = "just an example variable";
string anotherExampleVariable = "just another example variable";
string GetErrorMessage()
{
var isNotFound = Model.StatusCode == HttpStatusCode.NotFound;
string errorMessage;
if (isNotFound)
{
errorMessage = Resources.NotFoundMessage;
}
else
{
errorMessage = Resources.GeneralErrorMessage
}
return errorMessage;
}
}
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)%>