<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 have implemented the culture in my Asp.net application. But what I want is to change the language of the entire page but it's changing only model items language.
CultureHelper.cs
public class CultureHelper
{
protected HttpSessionState session;
public CultureHelper(HttpSessionState httpSessionState)
{
session = httpSessionState;
}
public static int CurrentCulture
{
get
{
switch (Thread.CurrentThread.CurrentUICulture.Name)
{
case "en": return 0;
case "hi-IN": return 1;
default: return 0;
}
}
set
{
if (value == 0)
{
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
}
else if(value==1)
{
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("hi-IN");
}
else
{
Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture;
}
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture;
}
}
}
BaseController.cs
protected override void ExecuteCore()
{
int culture = 0;
if (this.Session == null || this.Session["CurrentCulture"] == null)
{
int.TryParse(System.Configuration.ConfigurationManager.AppSettings["Culture"], out culture);
this.Session["CurrentCulture"] = culture;
}
else
{
culture = (int)this.Session["CurrentCulture"];
}
// calling CultureHelper class properties for setting
CultureHelper.CurrentCulture = culture;
base.ExecuteCore();
}
protected override bool DisableAsyncSupport
{
get { return true; }
}
My model class
[Display(Name="Name",ResourceType =typeof(Resource))]
Language only changing for the model class properties. But I want to change the language for static/nonmodel properties too. Like I want to change button text too. I have added all contents in the resource file. How can I achieve this?
Add a resource file for every culture you want to support, e.g.
Resources.en.resx
Resources.hi-IN.resx
The framework will resolve which file to use based on the set CurrentCulture.
Resources.resx (without culture name) will be used as fallback if no culture specific file can be found.
Use the resource file to retrieve your translated strings, e.g. in your View.cshtml:
#using MyProject.Resources
<button type="submit">
#Resources.Submit #* this retrieves the translation for the key "Submit" *#
</button>
#{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.
I need to validate 3 or more input fields (required at lest one). For example I have Email, Fax, Phone.
I require at least ONE to be filled in. I need both server and client 'unobtrusive validation'. please help. I looked into "Compare" method and tried modifying it but no luck. please help.
thanks
You could write a custom attribute:
public class AtLeastOneRequiredAttribute : ValidationAttribute, IClientValidatable
{
private readonly string[] _properties;
public AtLeastOneRequiredAttribute(params string[] properties)
{
_properties = properties;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (_properties == null || _properties.Length < 1)
{
return null;
}
foreach (var property in _properties)
{
var propertyInfo = validationContext.ObjectType.GetProperty(property);
if (propertyInfo == null)
{
return new ValidationResult(string.Format("unknown property {0}", property));
}
var propertyValue = propertyInfo.GetValue(validationContext.ObjectInstance, null);
if (propertyValue is string && !string.IsNullOrEmpty(propertyValue as string))
{
return null;
}
if (propertyValue != null)
{
return null;
}
}
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessage,
ValidationType = "atleastonerequired"
};
rule.ValidationParameters["properties"] = string.Join(",", _properties);
yield return rule;
}
}
which could be used to decorate one of your view model properties (the one you want to get highlighted if validation fails):
public class MyViewModel
{
[AtLeastOneRequired("Email", "Fax", "Phone", ErrorMessage = "At least Email, Fax or Phone is required")]
public string Email { get; set; }
public string Fax { get; set; }
public string Phone { get; set; }
}
and then a simple controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
Rendering the following view which will take care of defining the custom client side validator adapter:
#model MyViewModel
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script type="text/javascript">
jQuery.validator.unobtrusive.adapters.add(
'atleastonerequired', ['properties'], function (options) {
options.rules['atleastonerequired'] = options.params;
options.messages['atleastonerequired'] = options.message;
}
);
jQuery.validator.addMethod('atleastonerequired', function (value, element, params) {
var properties = params.properties.split(',');
var values = $.map(properties, function (property, index) {
var val = $('#' + property).val();
return val != '' ? val : null;
});
return values.length > 0;
}, '');
</script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(false)
<div>
#Html.LabelFor(x => x.Email)
#Html.EditorFor(x => x.Email)
</div>
<div>
#Html.LabelFor(x => x.Fax)
#Html.EditorFor(x => x.Fax)
</div>
<div>
#Html.LabelFor(x => x.Phone)
#Html.EditorFor(x => x.Phone)
</div>
<input type="submit" value="OK" />
}
Of course the custom adapter and validator rule should be externalized into a separate javascript file to avoid mixing script with markup.
I spent more than 36 hours why the code did not work for me.. At the end , I found out that in my case , I was not supposed to use the property names in this line of code
[AtLeastOneRequired("Email", "Fax", "Phone", ErrorMessage = "At least Email, Fax or Phone is required")]
But I had to use the HTMl element Ids in place of the property names and it worked like magic.
Posting this here if it might help somebody.
Since you are using MVC 3, take a look at great video Brad Wilson had on mvcConf. There's everything you need to create client + server Unobtrusive Validation
#Darin Dimitrov 's solution is probably the standard of creating a custom validation attribute that works with unobtrusive validation. However, using custom validation attributes for unobtrusive validation have some disadvantages such as:
The custom validation attribute is only attached to one properties, so client validation will not work if there's a change event on the other two inputs.
The error message works fine with ValidationSummary, but if you want to display 1 error message for the whole group (which I think is the norm), it would be nearly impossible.
To avoid the first problem, we can add custom validation attribute to each element in the group, which will cause another problem: we have to validate all elements of the group, instead of stopping at the first invalid group element. And of course, the second problem - separate error messages for each element - still remains.
There's another way to handle client side validation of group of inputs, using groups setting in jquery validator (https://jqueryvalidation.org/validate/#groups). The only problem (and a big one) is that unobtrusive validation doesn't support jquery validation's groups by default, so we have to customize a little bit.
Although this answer is hardly "unobtrusive", in my opinion, it is worth trying to get rid of unnecessary complication of code, if your final goal is to validate a group of inputs while using Microsoft's unobtrusive validator library.
First, because groups settings of default jquery validator is not available in jquery unobtrusive validator, we have to override unobtrusive settings (ref. How can I customize the unobtrusive validation in ASP.NET MVC 3 to match my style?)
$("form").on('submit', function () {
var form = this;
var validator = $(this).data("validator");
if (validator.settings && !validator.settings.submitHandler) {
$.extend(true, validator.settings.rules, validationSettings.rules);
$.extend(true, validator.settings.groups, validationSettings.groups);
initGroups(validator);
var fnErrorReplacement = validator.settings.errorPlacement;
validator.settings.errorPlacement = function (error, element) {
validationSettings.errorPlacement(error, element, fnErrorReplacement, form);
}
validator.settings.submitHandler = formSubmitHandler;
}
});
function formSubmitHandler(form) {
form.submit();
}
After that, override unobtrusive validator's groups, rules and errorPlacement settings.
var validationSettings = {
groups: {
checkboxgroup: "Email Fax Phone"
},
rules: {
Email: {
required: function () {
return validateCheckboxGroup(["#Email", "#Fax", "#Phone"]);
}
},
Fax: {
required: function () {
return validateCheckboxGroup(["#Email", "#Fax", "#Phone"]);
}
},
Phone: {
required: function () {
return validateCheckboxGroup(["#Email", "#Fax", "#Phone"]);
}
}
}
,
errorPlacement: function (error, element, fnUnobtrusive, form) {
switch (element.attr("name")) {
case "Email":
case "Fax":
case "Phone":
onGroupError(error, "CheckBoxGroup", form);
break;
default:
fnUnobtrusive(error, element);
break;
}
}
}
function validateCheckboxGroup(names) {
var result = true;
$.each(names, function (index, value) {
if ($(value).is(":checked")) {
result = false;
}
});
return result;
}
Because unobtrusive validator does not implement groups setting of jquery validator, we need to reuse two functions from the two libraries to: (1).split group names (reusing code from jquery validator) and (2) append error element without remove 'input-validation-error' class (reusing function onError from unobtrusive library).
function initGroups(validators) {
validators.groups = {};
$.each(validators.settings.groups,
function (key, value) {
if (typeof value === "string") {
value = value.split(/\s/);
}
$.each(value,
function (index, name) {
validators.groups[name] = key;
});
});
}
function onGroupError(error, inputElementName, form) {
var container = $(form).find("[data-valmsg-for='" + inputElementName + "']"),
replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null;
container.removeClass("field-validation-valid").addClass("field-validation-error");
error.data("unobtrusiveContainer", container);
if (replace) {
container.empty();
error.appendTo(container);
}
else {
error.hide();
}
}
Finally, use HtmlExtensions.ValidationMessage to create error span of the checkbox group.
#Html.ValidationMessage("CheckBoxGroup", new { #class = "text-danger" })
The keeping of "input-validation-error" class is necessary, so that jquery validator will validate all 3 elements (Email, Phone, Fax) of checkbox group as a whole, instead of validating one by one. The unobtrusive validation library remove this class by default on function onError, so we have to customize this as shown in function onGroupError above.
I am using the Sort method of the MvcContrib Grid to generate sorting links, e.g.
<%= Html.Grid(Model).AutoGenerateColumns().Sort((GridSortOptions)ViewData["sort"]) %>
I have a need to change the default controller/action that’s generated by the sort method. For example,
defaultControllerName/defaultActionName/?Column=ProductId&Direction=Ascending
would change to
customControllerName/customActionName/?Column=ProductId&Direction=Ascending
I haven't been able to find any existing methods in the MVCcontribution classes that would allow me to customise the links. I’d appreciate any pointers on how to go about altering the default links as I’m still very much a MVC/C# newbie.
That's not an easy task. You will need a custom grid renderer to achieve this and override the RenderHeaderText method:
public class MyHtmlTableGridRenderer<T> : HtmlTableGridRenderer<T> where TViewModel : class
{
protected override void RenderHeaderText(GridColumn<TViewModel> column)
{
if (IsSortingEnabled && column.Sortable)
{
// TODO: generate a custom link here based on the sorting options
string text = ...
base.RenderText(text);
}
else
{
RenderText(column.DisplayName);
}
}
}
And then specify that the grid should use this renderer:
.RenderUsing(new MyHtmlTableGridRenderer<Employee>())
I wanted to provide a complete working example:
public class SortableHtmlTableGridRenderer<T> : HtmlTableGridRenderer<T> where T : class
{
readonly string _action;
readonly string _controllerName;
public SortableHtmlTableGridRenderer(string action, string controllerName)
{
_action = action;
_controllerName = controllerName;
}
protected override void RenderHeaderText(GridColumn<T> column)
{
if (IsSortingEnabled && column.Sortable)
{
string sortColumnName = GenerateSortColumnName(column);
bool isSortedByThisColumn = GridModel.SortOptions.Column == sortColumnName;
var sortOptions = new GridSortOptions
{
Column = sortColumnName
};
if (isSortedByThisColumn)
{
sortOptions.Direction = (GridModel.SortOptions.Direction == SortDirection.Ascending)
? SortDirection.Descending
: SortDirection.Ascending;
}
else //default sort order
{
sortOptions.Direction = column.InitialDirection ?? GridModel.SortOptions.Direction;
}
var routeValues = HtmlHelper.AnonymousObjectToHtmlAttributes(new {sortOptions.Column, sortOptions.Direction });
var text = HtmlHelper.GenerateLink(Context.RequestContext, RouteTable.Routes, column.DisplayName, null, _action, _controllerName, routeValues, null);
RenderText(text);
}
else
{
RenderText(column.DisplayName);
}
}
}
Usage:
.RenderUsing(new SortableHtmlTableGridRenderer<YourModelType>("Search", "Search"))