Set current, before current and after current elements in menu - asp.net-mvc-3

For setting that I use Html helper method which is not the best imo, because I use static field.
public enum CurrentState
{
BeforeCurrent,
AfterCurrent
}
public static CurrentState currentState = CurrentState.BeforeCurrent;
public static MvcHtmlString ActiveActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName, bool checkAction = true)
{
string currentAction = helper.ViewContext.RouteData.GetRequiredString("action");
string currentController = helper.ViewContext.RouteData.GetRequiredString("controller");
if ((controllerName == currentController) && checkAction && (actionName == "Index"))
{
currentState = CurrentState.BeforeCurrent;
}
if ((controllerName == currentController) && checkAction && (actionName != currentAction))
{
if (currentState == CurrentState.BeforeCurrent)
{
return helper.ActionLink(linkText, actionName, controllerName, null, new { #class = "beforeCurrent" });
}
else if (currentState == CurrentState.AfterCurrent)
{
return helper.ActionLink(linkText, actionName, controllerName, null, new { #class = "afterCurrent" });
}
}
if ((controllerName == currentController) && (!checkAction || (actionName == currentAction)))
{
currentState = CurrentState.AfterCurrent;
return helper.ActionLink(linkText, actionName, controllerName, null, new { #class = "current" });
}
return helper.ActionLink(linkText, actionName, controllerName);
}
I have two levels of menus and that's why I use checkAction parameter:
main menu - #Html.ActiveActionLink(Resources.Global.mainMenuBoard, "Index", "Board", checkAction: false)
side menu - #Html.ActiveActionLink(#Resources.Global.managementOverview, "Index", "Management")
and in side menu I need to know if it's after and before current (overlapping items...).
Is it a way to improve that?
Additionally I must say that I use javascript also for that but it must work also for javascript disabled.

I finally solve that by generating whole menu in one helper:
public class Link
{
public string LinkText { get; set; }
public string ActionName { get; set; }
}
public static List<MvcHtmlString> SubMenuLinks(this HtmlHelper helper, string controllerName, List<Link> links)
{
List<MvcHtmlString> menuElements = new List<MvcHtmlString>();
string actualCssClass = "beforeCurrent";
string currentAction = helper.ViewContext.RouteData.GetRequiredString("action");
string currentController = helper.ViewContext.RouteData.GetRequiredString("controller");
foreach (Link link in links)
{
if (controllerName == currentController && link.ActionName == currentAction)
{
menuElements.Add(helper.ActionLink(link.LinkText, link.ActionName, controllerName, null, new { #class = "current" }));
actualCssClass = "afterCurrent";
}
else
{
menuElements.Add(helper.ActionLink(link.LinkText, link.ActionName, controllerName, null, new { #class = actualCssClass }));
}
}
return menuElements;
}
and in view:
#{
List<MvcHtmlString> actionMenu = Html.SubMenuLinks("Manager", new List<Link>()
{
new Link() { LinkText = "linkText", ActionName = "actionName" },
new Link() { LinkText = "linkText2", ActionName = "actionName2" }
});
}
#section SideMenu
{
#for (int i = 0; i < actionMenu.Count; i++)
{
<li id="menu#(i)">#actionMenu.ElementAt(i)</li>
}
}
Not perfect, but it works at least.

Related

Complex Custom Validation in Foolproof Validation

I have implemented Complex custom Foolproof validation in my application from this link but sadly its not working.My requirement is simple,I have a input file for uploading an image and there should be a validation if the user chooses to upload file other than specified below
".jpg",".png",".gif",".jpeg"
Code is
[Required(ErrorMessage = "Please upload Photo", AllowEmptyStrings = false)]
[IsValidPhoto(ErrorMessage="Please select files of type .jpg,.png,.gif,.jpeg")]
public HttpPostedFileBase PhotoUrl { get; set; }
public class IsValidPhotoAttribute : ModelAwareValidationAttribute
{
//this is needed to register this attribute with foolproof's validator adapter
static IsValidPhotoAttribute() { Register.Attribute(typeof(IsValidPhotoAttribute)); }
public override bool IsValid(object value, object container)
{
if (value != null)
{
string[] AllowedFileExtensions = new string[] { ".jpg", ".gif", ".png", ".jpeg" };
var file = value as HttpPostedFileBase;
if (!AllowedFileExtensions.Contains(file.FileName.Substring(file.FileName.LastIndexOf('.'))))
{
return false;
}
}
return true;
}
}
CSHTML is
#Html.TextBoxFor(m => m.PhotoUrl, new { #class = "form-control imgUpload",
#placeholder = "Please upload Photo", #id = "txtPhoto", #type = "file" })
#Html.ValidationMessageFor(m => m.PhotoUrl)
You will not be able to get client side validation unless you also create a script to add the rules. It is not necessary to use foolproof and the following method and scripts will give you both server and client side validation
public class FileAttachmentAttribute : ValidationAttribute, IClientValidatable
{
private List<string> _Extensions { get; set; }
private const string _DefaultErrorMessage = "Only file types with the following extensions are allowed: {0}";
public FileAttachmentAttribute(string fileExtensions)
{
_Extensions = fileExtensions.Split('|').ToList();
ErrorMessage = _DefaultErrorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
HttpPostedFileBase file = value as HttpPostedFileBase;
if (file != null)
{
var isValid = _Extensions.Any(e => file.FileName.EndsWith(e));
if (!isValid)
{
return new ValidationResult(string.Format(ErrorMessageString, string.Join(", ", _Extensions)));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ValidationType = "fileattachment",
ErrorMessage = string.Format(ErrorMessageString, string.Join(", ", _Extensions))
};
rule.ValidationParameters.Add("extensions", string.Join(",", _Extensions));
yield return rule;
}
}
Scripts
$.validator.unobtrusive.adapters.add('fileattachment', ['extensions'], function (options) {
var params = { fileattachment: options.params.extensions.split(',') };
options.rules['fileattachment'] = params;
if (options.message) {
options.messages['fileattachment'] = options.message;
}
});
$.validator.addMethod("fileattachment", function (value, element, param) {
var extension = getExtension(value);
return $.inArray(extension, param.fileextensions) !== -1;
});
function getExtension(fileName) {
var extension = (/[.]/.exec(fileName)) ? /[^.]+$/.exec(fileName) : undefined;
if (extension != undefined) {
return extension[0];
}
return extension;
};
and then use it as
[FileAttachment("jpg|gif|png|jpeg")]
public HttpPostedFileBase PhotoUrl { get; set; }

url.Action with MvcContrib generates invalid links

In our application we use MvcContrib for generating links with the exception of cross area links where Contrib seems to be not working properly (or we are doing something wrong). In services we have a function that generates a List< ZakladkaModel > which contains url and other properties used in generating tabstrib via custom html helper. That function takes as an argument an id of database object and UrlHelper to help in link creating.
m_service.GenerowanieZakladkiDlaKontrolera_ARCH_Akt(idAktu, new UrlHelper(this.ControllerContext.RequestContext));
Then in the GenerowanieZakladkiDlaKontrolera_ARCH_Akt we have something like this:
model.Add(new ZakladkaModel { Aktywnosc = true, NazwaZakladki = "Akt", Url = "" });
model.Add(new ZakladkaModel { Aktywnosc = true, NazwaZakladki = "Wzmianki", Url = url.Action<Usc.Presentation.Areas.FU_RAU.Controllers.ARCH.ARCH_WzmiankiController>(c => c.Index(idAktu)) });
if (tekstJednolity.StanTekstuJednolitego == "RB" || tekstJednolity.StanTekstuJednolitego == "SW")
{
model.Add(new ZakladkaModel { Aktywnosc = true, NazwaZakladki = "t.j. aktu", Url = url.Action<Usc.Presentation.Areas.FU_RAU.Controllers.ARCH.ARCH_TekstJednolityController>(c => c.Edytuj(tekstJednolity.Id)) });
}
else
{
model.Add(new ZakladkaModel { Aktywnosc = true, NazwaZakladki = "t.j. aktu", Url = url.Action<Usc.Presentation.Areas.FU_RAU.Controllers.ARCH.ARCH_TekstJednolityController>(c => c.Raport(tekstJednolity.Id)) });
}
model.Add(new ZakladkaModel { Aktywnosc = true, NazwaZakladki = "Przypisek 1", Url = url.Action<Usc.Presentation.Areas.FU_RAU.Controllers.ARCH.Przypisek1Controller>(c => c.Index(idAktu)) });
model.Add(new ZakladkaModel { Aktywnosc = true, NazwaZakladki = "Przypisek 2", Url = url.Action<Usc.Presentation.Areas.FU_RAU.Controllers.ARCH.Przypisek2Controller>(c => c.Index(idAktu)) });
model.Add(new ZakladkaModel { Aktywnosc = true, NazwaZakladki = "Przypisek 3", Url = url.Action<Usc.Presentation.Areas.FU_RAU.Controllers.ARCH.Przypisek3Controller>(c => c.Edytuj(idAktu)) });
model.Add(new ZakladkaModel { Aktywnosc = true, NazwaZakladki = "Przypisek 4", Url = url.Action<Usc.Presentation.Areas.FU_RAU.Controllers.ARCH.Przypisek4Controller>(c => c.Edytuj(idAktu)) });
Now the problem is that on some co-workers computers it generates links to actions properly and on some it looks like it takes a ranedom area from our app and tries to make an invalid link. We could use a simple url.Action("action","controler") which works fine on all but we would prefer MvcContrib :). Does anyone have any idea why this occurs? Or can share an alternative?
It seems that LinkBuilder which is used under doesn't use GetVirtualPatchForArea at all which as I read is MVC bug. So i decided to make my own HtmlHelper which uses that method:
public static string ActionArea<TController>(this HtmlHelper urlHelper, Expression<Action<TController>> expression) where TController : Controller
{
RouteValueDictionary routeValues = GetRouteValuesFromExpression(expression);
VirtualPathData vpd = new UrlHelper(urlHelper.ViewContext.RequestContext).RouteCollection.GetVirtualPathForArea(urlHelper.ViewContext.RequestContext, routeValues);
return (vpd == null) ? null : vpd.VirtualPath;
}
public static string ActionArea<TController>(this UrlHelper urlHelper, Expression<Action<TController>> expression) where TController : Controller
{
RouteValueDictionary routeValues = GetRouteValuesFromExpression(expression);
VirtualPathData vpd = urlHelper.RouteCollection.GetVirtualPathForArea(urlHelper.RequestContext, routeValues);
return (vpd == null) ? null : vpd.VirtualPath;
}
public static RouteValueDictionary GetRouteValuesFromExpression<TController>(Expression<Action<TController>> action) where TController : Controller
{
if (action == null)
{
throw new ArgumentNullException("action");
}
MethodCallExpression call = action.Body as MethodCallExpression;
if (call == null)
{
throw new ArgumentException("Akcja nie może być pusta.", "action");
}
string controllerName = typeof(TController).Name;
if (!controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Docelowa klasa nie jest kontrolerem.(Nie kończy się na 'Controller')", "action");
}
controllerName = controllerName.Substring(0, controllerName.Length - "Controller".Length);
if (controllerName.Length == 0)
{
throw new ArgumentException("Nie można przejść do kontrolera.", "action");
}
// TODO: How do we know that this method is even web callable?
// For now, we just let the call itself throw an exception.
string actionName = GetTargetActionName(call.Method);
var rvd = new RouteValueDictionary();
rvd.Add("Controller", controllerName);
rvd.Add("Action", actionName);
var namespaceNazwa = typeof(TController).Namespace;
if(namespaceNazwa.Contains("Areas."))
{
int index = namespaceNazwa.IndexOf('.',namespaceNazwa.IndexOf("Areas."));
string nazwaArea = namespaceNazwa.Substring(namespaceNazwa.IndexOf("Areas.") + 6, index - namespaceNazwa.IndexOf("Areas.") + 1);
if (!String.IsNullOrEmpty(nazwaArea))
{
rvd.Add("Area", nazwaArea);
}
}
//var typ = typeof(TController).GetCustomAttributes(typeof(ActionLinkAreaAttribute), true /* inherit */).FirstOrDefault();
/*ActionLinkAreaAttribute areaAttr = typ as ActionLinkAreaAttribute;
if (areaAttr != null)
{
string areaName = areaAttr.Area;
rvd.Add("Area", areaName);
}*/
AddParameterValuesFromExpressionToDictionary(rvd, call);
return rvd;
}
private static string GetTargetActionName(MethodInfo methodInfo)
{
string methodName = methodInfo.Name;
// do we know this not to be an action?
if (methodInfo.IsDefined(typeof(NonActionAttribute), true /* inherit */))
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture,
"Nie można wywoływać metod innych niż akcje.", methodName));
}
// has this been renamed?
ActionNameAttribute nameAttr = methodInfo.GetCustomAttributes(typeof(ActionNameAttribute), true /* inherit */).OfType<ActionNameAttribute>().FirstOrDefault();
if (nameAttr != null)
{
return nameAttr.Name;
}
// targeting an async action?
if (methodInfo.DeclaringType.IsSubclassOf(typeof(AsyncController)))
{
if (methodName.EndsWith("Async", StringComparison.OrdinalIgnoreCase))
{
return methodName.Substring(0, methodName.Length - "Async".Length);
}
if (methodName.EndsWith("Completed", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture,
"Nie można wywoływać kompletnych metod.", methodName));
}
}
// fallback
return methodName;
}
static void AddParameterValuesFromExpressionToDictionary(RouteValueDictionary rvd, MethodCallExpression call)
{
ParameterInfo[] parameters = call.Method.GetParameters();
if (parameters.Length > 0)
{
for (int i = 0; i < parameters.Length; i++)
{
Expression arg = call.Arguments[i];
object value = null;
ConstantExpression ce = arg as ConstantExpression;
if (ce != null)
{
// If argument is a constant expression, just get the value
value = ce.Value;
}
else
{
value = CachedExpressionCompiler.Evaluate(arg);
}
rvd.Add(parameters[i].Name, value);
}
}
}
Hope this helps people with similiar problems. Some of the code above i got from mvc2-rtm-sources modified to my needs http://aspnet.codeplex.com/releases/view/41742

Asp.net MVC3 custom validator created using dataannotation showing messages in incorrect place

I am using Asp.net MVC3, razor view engine and data annotation for model validation.
I have a form in which have to input Url details(Url and Description).Both fields are not required. But if i input one field other must be required.If i input description Url is required and is in correct format and if i enter Url then description is required.
I created a customvalidator for data annotation .It validates and output error message.
But my problem is error message generated by ValidationMessageFor is in incorrect place.
ie,if i enter description ,the required url message, is part of description.
I expect that message as part of ValidationMessageFor url field.
Can any one can help me? Thanks in advance. Folowing are the code i used
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class IsExistAttribute : ValidationAttribute, IClientValidatable
{
private const string DefaultErrorMessage = "{0} is required.";
public string OtherProperty { get; private set; }
public IsExistAttribute (string otherProperty)
: base(DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherProperty);
}
protected override ValidationResult IsValid(object value,ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType()
.GetProperty(OtherProperty);
var otherPropertyValue = otherProperty
.GetValue(validationContext.ObjectInstance, null);
var strvalue=Convert.ToString(otherPropertyValue)
if (string.IsNullOrEmpty(strvalue))
{
//return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName),new[] { OtherProperty});
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "isexist"
};
clientValidationRule.ValidationParameters.Add("otherproperty", OtherProperty);
return new[] { clientValidationRule };
}
}
in model
[Display(Name = "Description")]
[IsExist("Url")]
public string Description { get; set; }
[Display(Name = "Url")]
[IsExist("Description")]
[RegularExpression("(http(s)?://)?([\w-]+\.)+[\w-]+(/[\w- ;,./?%&=]*)?", ErrorMessage = "Invalid Url")]
public string Url { get; set; }
and in view
<div class="editor-field">
#Html.TextBoxFor(m => m.Description )
#Html.ValidationMessageFor(m => m.Description)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.Url)
#Html.ValidationMessageFor(m => m.Url)
</div>
unobstrusive validation logic
(function ($) {
$.validator.addMethod("isexist", function (value, element, params) {
if (!this.optional(element)) {
var otherProp = $('#' + params)
return (otherProp.val() !='' && value!='');//validation logic--edited by Rajesh
}
return true;
});
$.validator.unobtrusive.adapters.addSingleVal("isexist", "otherproperty");
} (jQuery));
You should make the validation the other way round. Change:
if (string.IsNullOrEmpty(otherPropertyValue))
{
//return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName),new[] { OtherProperty});
}
To:
if (string.IsNullOrEmpty(Convert.ToString(value)) && !string.IsNullOrEmpty(otherPropertyValue))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
And remove if (value != null)
The field that will then get invalidated is the empty one, in the case the other one is filled.

string array to be separated with comma

I had a string array which I want it to be returned in view separated by comma.
#Html.DisplayFor(m => name.studentName) <span>, </span>}
I'm using this way but the last string will ended with a comma also. Wondering how to avoid this?
I assume that you have a collection of students on your model each possessing a studentName property that you want to display:
public IEnumerable<Student> Students { get; set; }
And inside your view you are looping through this collection and displaying each student name individually.
Now instead of looping you could do the following:
#Html.Raw(
string.Join(
"<span>,<span>",
Model.Students.Select(x => Html.Encode(x.studentName))
)
)
or even better, externalize this logic into a reusable custom HTML helper:
public static class HtmlExtensions
{
public static IHtmlString FormatStudentNames(this HtmlHelper htmlHelper, IEnumerable<Student> students)
{
return new HtmlString(
string.Join(
"<span>,<span>",
students.Select(x => Html.Encode(x.studentName))
)
);
}
}
and then inside your view simply call this helper:
#Html.FormatStudentNames(Model.Students)
You no longer need to write any foreach or whatever loops you are writing.
Try
#string.Join(",", name.studentName);
And have a look at string.Join on MSDN.
$(".category").change(function () {
var value = $(this).val();
loadSubCategory(value)
});
function loadSubCategory(parentID) {
var $el = $("#SubCats");
$el.prop("disabled", true);
$el.empty();
$.ajax({
cache: false,
url: "/Category/loadSubCategory?id=" + parentID,
success: function (data) {
if (data != '' && data != null) {
if (data != 'error') {
var sch = JSON.parse(data);
if (sch.length > 0) {
$el.prop("disabled", false);
for (i = 0; i < sch.length; i++) {
$el.append($("<option></option>")
.attr("value", sch[i].ID).text(sch[i].Description));
}
}
}
}
}
});
}
public ActionResult loadSubCategory(string id)
{
string list = "";
try
{
list = Newtonsoft.Json.JsonConvert.SerializeObject(menu.SubCategory(id));
}
catch (Exception ex)
{
}
return Content(list);
}
public List<CategoryModel> SubCategory(string parentID){
List<CategoryModel> listCategory= new List<CategoryModel>();
string[] yourValues = parentID.Split(',');
foreach (var item in yourValues)
{
var Category = UowObj.CategoryRepository.Get(filter: c => c.ParentId.ToString() == item && c.IsActive == true).ToList();
if (Category != null)
{
var category= new CategoryModel();
foreach (var itemq in Category)
{
category.ID = itemq.ID;
category.Description = itemq.Description;
}
listCategory.Add(merchant);
}
}

client side validation in MVC3 not working

below is the code somehow client side validation is not working...I searched couple of questions in this forum and wrote this..
here is the custom validation attribute "startDateAttribute"
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class StartDateAttribute : ValidationAttribute, IClientValidatable
{
public StartDateAttribute ()
{
}
public override bool IsValid(object value)
{
var date = (DateTime)value;
if (date.Date >= DateTime.Now.Date)
{
return true;
}
return false;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage,
ValidationType = "DateRange"
};
}
}
[CurrentDateAttribute(ErrorMessage = "select the correct date")]
public DateTime? StartDate { get; set; }
here is the JQuery code added
jQuery.validator.addMethod('DateRange', function (value, element, params) {
var d = new Date();
var currentDate = (d.getMonth()+1) + "/"+d.getDate()+ "/" + d.getFullYear() ;
return value >= currentDate;
});
// and an unobtrusive adapter
jQuery.validator.unobtrusive.adapters.add('DateRange', { }, function (options) {
options.rules['DateRange'] = true;
options.messages['DateRange'] = options.message;
});
One of the requirements of client side validation is that the ValidationType and the adapter name should match and should be lower case.
Change the ValidationType and adapter name to 'daterange' and check

Resources