I'm working on MVC 5 project and I have an unit test that checks the generated html markup for a custom html helper.
private class Faq
{
[RequiredIfMultiple(new string[] { "SickLeave", "Holidays" }, new object[] { 2, 2 })]
[RequiredIfMultiple(new string[] { "SickLeave1", "SickLeave2", "SickLeave3" }, new object[] { 2, 2, 2 })]
public string Property { get; set; }
}
[Test]
public void HtmlString_With_RequiredIfMultiple_Test()
{
//Arrange
Expression<Func<Faq, string>> expression = (t => t.Property);
//Act
IHtmlString result = htmlHelper.EditorForRequiredIf(expression);
// Assert
Assert.IsTrue(result.ToString().Contains("data-val-requiredifmultiple"));
}
The html helper extension EditorForRequiredIf
public static MvcHtmlString EditorForRequiredIf<TModel, TValue>(this HtmlHelper<TModel> helper
, Expression<Func<TModel, TValue>> expression
, string templateName = null
, string htmlFieldName = null
, object additionalViewData = null)
{
string mvcHtml = EditorExtensions.EditorFor(helper, expression, templateName, htmlFieldName, additionalViewData).ToString();
string element = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression));
string key = helper.ViewData.Model.ToString() + "." + element;
if (RequiredIfMultipleAttribute.CountPerField != null)
{
RequiredIfMultipleAttribute.CountPerField.Remove(key);
if (RequiredIfMultipleAttribute.CountPerField.Count == 0)
{
RequiredIfMultipleAttribute.CountPerField = null;
}
}
string pattern = #"data\-val\-requiredif[a-z]+";
return Regex.IsMatch(mvcHtml, pattern) ? MergeClientValidationRules(mvcHtml) : MvcHtmlString.Create(mvcHtml);
}
Once in the view, the EditorFor calls GetClientValidationRules method on a custom attribute RequiredIfMultipleAttribute to generate the html markup with proper data-val tags.
GetClientValidationRules method:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
int count = 0;
string key = metadata.ContainerType.FullName + "." + metadata.PropertyName;
if (CountPerField == null)
{
CountPerField = new Dictionary<string, int>();
}
if (CountPerField.ContainsKey(key))
{
count = ++CountPerField[key];
}
else
{
CountPerField.Add(key, count);
}
yield return new RequiredIfMultipleValidationRule(ErrorMessageString, Props, Vals, count);
}
So in production, all of this works perfectly. But during tests, I'm having an awful time.
What I get is an empty string on string mvcHtml = EditorExtensions.EditorFor(helper, expression, templateName, htmlFieldName, additionalViewData).ToString(); and on the calls stack, I don't see the method GetClientValidationRules has been called.
On the other hand, if I change EditorExtensions.EditorFor by InputExtensions.TextBoxFor, I see that the MvcHtmlString is correctly generated and GetClientValidationRules was called.
Does anyone have a clue? and I hope I was clear enough.
For the atttribute to work, some piece of code that executes your method has to recognize it and act on it in some way. When you are running your code in production that's what Asp.Net MVC does for you.
Your test is run by NUnit, not by Asp.Net. NUnit doesn't do anything with the attribute because it's not designed to do that. If you think about it, you'll realize that NUnit could not possibly recognize and act on all the various attributes known to all the hosts in which production code may run.
As a result, any use you make of that attribute has to be in your MVC application itself, not in your test.
Related
Could some one please help me to resolved this? i'm trying to change CustomAsync to MustAsync, but i couldn't make things to work. Below is my custom method
RuleFor(o => o).MustAsync(o => {
return CheckIdNumberAlreadyExist(o)
});
private static async Task<ValidationFailure> CheckIdNumberAlreadyExist(SaveProxyCommand command)
{
if (command.Id > 0)
return null;
using (IDbConnection connection = new SqlConnection(ConnectionSettings.LicensingConnectionString))
{
var param = new DynamicParameters();
param.Add("#idnumber", command.IdNumber);
var vehicle = await connection.QueryFirstOrDefaultAsync<dynamic>("new_checkDuplicateProxyIdNumber", param, commandType: CommandType.StoredProcedure);
return vehicle != null
? new ValidationFailure("IdNumber", "Id Number Already Exist")
: null;
}
}
To make it work with the latest version of the FluentValidation, I had to use the codes like below.
RuleFor(ws => ws).MustAsync((x, cancellation) => UserHasAccess(x)).WithMessage("User doesn't have access to perform this action");
Please notice the lambda expression here MustAsync((x, cancellation) => UserHasAccess(x)), without this I was always getting an error as cannot convert from 'method group' to 'Func<Worksheet, CancellationToken, Task<bool>>
Below is my custom UserHasAccess function.
private async Task <bool> UserHasAccess(Worksheet worksheet) {
var permissionObject = await _dataProviderService.GetItemAsync(worksheet.FileItemId);
if (permissionObject is null) return false;
if (EditAccess(permissionObject.Permission)) return true;
return false;
}
I'm assuming you're using a version of FluentValidation prior to version 6, as you're not passing in a Continuation Token, so I've based my answer on version 5.6.2.
Your example code does not compile, for starters, as you're missing a semi-colon in your actual rule. You are also evaluating two different properties on the SaveProxyCommand parameter.
I've built a very small POC based on some assumptions:
Given 2 classes:
public class SaveProxyCommand {
public int Id { get; set; }
}
public class ValidationFailure {
public string PropertyName { get; }
public string Message { get; }
public ValidationFailure(string propertyName, string message){
Message = message;
PropertyName = propertyName;
}
}
And a validator:
public class SaveProxyCommandValidator : AbstractValidator<SaveProxyCommand>{
public SaveProxyCommandValidator()
{
RuleFor(o => o).MustAsync(CheckIdNumberAlreadyExists)
.WithName("Id")
.WithState(o => new ValidationFailure(nameof(o.IdNumber), "Id Number Already Exist"));
}
private static async Task<bool> CheckIdNumberAlreadyExists(SaveProxyCommand command) {
if (command.Id > 0)
return true;
var existingIdNumbers = new[] {
1, 2, 3, 4
};
// This is a fudge, but you'd make your db call here
var isNewNumber = !(await Task.FromResult(existingIdNumbers.Contains(command.IdNumber)));
return isNewNumber;
}
}
I didn't include the call to the database, as that's not part of your problem. There are a couple of things of note here:
You're not setting the .WithName annotation method, but when you're setting up a validation rule for an object you have to do this, as FluentValidation expects you to specify specific properties to be validated by default, if you pass in an entire object it just doesn't know how to report errors back.
Must/MustAsync need to return a bool/Task<bool> instead of a custom object. To get around this, you can specify a custom state to be returned when failing validation.
You can then get access to this like this:
var sut = new SaveProxyCommand { Id = 0, IdNumber = 3 };
var validator = new SaveProxyCommandValidator();
var result = validator.ValidateAsync(sut).GetAwaiter().GetResult();
var ValidationFailures = result.Errors?.Select(s => s.CustomState).Cast<ValidationFailure>();
The above does not take into account empty collections, it's just an example of how to dig into the object graph to retrieve custom state.
As a suggestion, fluentvalidation works best if you set up individual rules per property, instead of validating the entire object. My take on this would be something like this:
public class SaveProxyCommandValidator : AbstractValidator<SaveProxyCommand>{
public SaveProxyCommandValidator()
{
RuleFor(o => o.IdNumber).MustAsync(CheckIdNumberAlreadyExists)
.Unless(o => o.Id > 0)
.WithState(o => new ValidationFailure(nameof(o.IdNumber), "Id Number Already Exist"));
}
private static async Task<bool> CheckIdNumberAlreadyExists(int numberToEvaluate) {
var existingIdNumbers = new[] {
1, 2, 3, 4
};
// This is a fudge, but you'd make your db call here
var isNewNumber = !(await Task.FromResult(existingIdNumbers.Contains(numberToEvaluate)));
return isNewNumber;
}
}
This read more like a narrative, it uses the .Unless construct to only run the rule if Id is not more than 0, and does not require the evaluation of the entire object.
I am currently developing a Framework to generate dynamic Views in MVC, the idea is based on this tutorial.
The next step is adding the possibility to generate multiple submit buttons but I cant get it to work. I did some research and found this approach. However, since i want to generate those buttons dynamically, this does not work yet.
What I tried is to modify this attribute code here:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MultipleButtonAttribute : ActionNameSelectorAttribute
{
public string Name { get; set; }
public string Argument { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
var isValidName = false;
var keyValue = string.Format("{0}:{1}", Name, Argument);
var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);
if (value != null)
{
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument;
isValidName = true;
}
return isValidName;
}
}
While debugging I digged down the ValueProvider and found out that the JQueryFormValueProvider in the Collection of Valueproviders actually contains the Name of the clicked submitbutton.
The button is generated like this:
<input type="submit" name="buttonClick:#Model.Id" value="#Model.Text" />
Unfortunately the ValueProviders do not let me Iterate through the Keys so I dont know how to get the value I circled red in the above screenshot.
It doesnt need to be this approach, all what counts is, to find out which button was clicked.
Ok i found the solution by modifying the Attribute-code like this:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class NameToRouteDataAttribute : ActionNameSelectorAttribute
{
/// <Summary>Name of the Value you want to grab from the control.</Summary>
public string Name { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
ValueProviderCollection providers = controllerContext.Controller.ValueProvider as ValueProviderCollection;
if (providers != null)
{
// We need this value-Provider as it contains the Data from the Form
JQueryFormValueProvider formProvider = providers.OfType<JQueryFormValueProvider>().FirstOrDefault();
if (formProvider != null)
{
// now look for the specified value-prefix.
var kvp = formProvider.GetKeysFromPrefix(Name).FirstOrDefault();
if (kvp.Key != null)
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = kvp.Key;
}
}
return true;
}
}
Contollercode:
[HttpPost]
[NameToRouteDataAttribute(Name = "buttonClick")]
public ActionResult Show(FormViewModel form)
{
string buttonId = RouteData.GetRequiredString("buttonClick");
...
}
Buttoncode
#model MvcForms.Controls.ButtonViewModel
<input type="submit" name="buttonClick.#Model.Id" value="#Model.Text" />
The important thing here is, that the name of the Button contains a dot. This makes the JQueryFormValueProvider.GetKeysFromPrefix work the way I need it to.
I am using RazorEngine to parse templates from html snippets on a web page. (This is a legacy system that switching to Mvc Razor views isn't possible, so we are switching small sections over to using RazorEngine where it makes sense). There are many of questions on SO and the internet trying to get Mvc's Html and Url helpers to work with Razor engine. To get #Html syntax to work, I've modified some code found here to add Html to the base template:
public HtmlHelper<t> Html
{
get
{
if (helper == null)
{
var writer = this.CurrentWriter; //TemplateBase.CurrentWriter
var vcontext = new ViewContext() { Writer = writer, ViewData = this.ViewData};
helper = new HtmlHelper<t>(vcontext, this);
}
return helper;
}
}
public ViewDataDictionary ViewData
{
get
{
if (viewdata == null)
{
viewdata = new ViewDataDictionary();
viewdata.TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = string.Empty };
if (this.Model != null)
{
viewdata.Model = Model;
}
}
return viewdata;
}
set
{
viewdata = value;
}
}
After a lot of debugging into the Html source code I think I've managed to instantiate everything that the Html helper needs, and it runs successfully for #Html.Label... The problem is that the resulting html is:
<label for="MyNumber">MyNumber</label>
When it obviously should be:
<label for="MyNumber">MyNumber</label>
I am stumped as to how to fix this. I was not able to find how the encoding happens when looking through the RazorEngine source. My initial thought was that the TextWriter must be encoding the value but I have not been able to confirm this. How can I get #Html.BlahFor() to render un-escaped html?
I have found a workaround for the problem I was facing. RazorEngine's base Template will automatically encode a string (or object) if it doesn't cast to an IEncodedString. In my case, I solved this issue by overriding the WriteTo method in my template class:
public override void WriteTo(TextWriter writer, object value)
{
if (writer == null)
throw new ArgumentNullException("writer");
if (value == null) return;
var encodedString = value as IEncodedString;
if (encodedString != null)
{
writer.Write(encodedString);
}
else
{
var htmlString = value as IHtmlString;
if(htmlString != null)
writer.Write(htmlString.ToHtmlString());
else
{
//This was the base template's implementation:
encodedString = TemplateService.EncodedStringFactory.CreateEncodedString(value);
writer.Write(encodedString);
}
}
}
I have yet to see if this change will cause any errors, but now that I found the method it should be relatively easy to change in the future.
EDIT: Checked to see Html helpers return an MvcHtmlString, which implements IHtmlString, so by adding a cast to this interface, we can avoid encoding the html returned by the helpers, while still having a safety in place for any other callers of this method.
Antaris, author of RazorEngine, has supplied another interesting way for handling this issue, and explained by the way why it is not done by default (for reducing as much as possible RazorEngine dependencies).
Here is for your case the relevant part of his answer on the corresponding github issue:
I think the easiest
thing to do here, would be to implement a custom
IEncodedStringFactory which handles MvcHtmlString. Something like:
public class MvcHtmlStringFactory : IEncodedStringFactory
{
public IEncodedString CreateEncodedString(string rawString)
{
return new HtmlEncodedString(rawString);
}
public IEncodedString CreateEncodedString(object obj)
{
if (obj == null)
return new HtmlEncodedString(string.Empty);
var htmlString = obj as HtmlEncodedString;
if (htmlString != null)
return htmlString;
var mvcHtmlString = obj as MvcHtmlString;
if (mvcHtmlString != null)
return new MvcHtmlStringWrapper(mvcHtmlString);
return new HtmlEncodedString(obj.Tostring());
}
}
public MvcHtmlStringWrapper : IEncodedString
{
private readonly MvcHtmlString _value;
public MvcHtmlStringWrapper(MvcHtmlString value)
{
_value = value;
}
public string ToEncodedString()
{
return _value.ToString();
}
public override string ToString()
{
return ToEncodedString();
}
}
This would enable existing MvcHtmlString instances to bypass
RazorEngine's built in encoding mechanism. This would not be part of
the base library because we don't take any dependencies on
System.Web, or System.Web.Mvc.
You would need to wire this up via your configuration:
var config = new TemplateServiceConfiguration()
{
BaseTemplateType = typeof(MvcTemplateBase<>),
EncodedStringFactory = new MvcHtmlStringFactory()
};
Personally, I have switched MvcHtmlString for IHtmlString before using his code. As of MVC 5, MvcHtmlString does implement it too. (Beware, it was not the case in some older MVC version.)
Thanks for the inspiration, I had my data stored in a xml and created a different workaround without overwriting the WriteTo method. I used a class that inherited from the TemplateBase and created 2 new methods.
public IEncodedString HtmlOf(NBrightInfo info, String xpath)
{
var strOut = info.GetXmlProperty(xpath);
strOut = System.Web.HttpUtility.HtmlDecode(strOut);
return new RawString(strOut);
}
public IEncodedString BreakOf(NBrightInfo info, String xpath)
{
var strOut = info.GetXmlProperty(xpath);
strOut = System.Web.HttpUtility.HtmlEncode(strOut);
strOut = strOut.Replace(Environment.NewLine, "<br/>");
strOut = strOut.Replace("\t", " ");
strOut = strOut.Replace("'", "'");
return new RawString(strOut);
}
In this post I wondered about cleaner code when internationalising an app. that leads to this second query... supposing I wanted to call a function like this:
#Html.RenderWithTags("Help",
new Dictionary<string, string>() { "HelpPage", "#Html.ActionLink(...)" }
)
such that I look up a string in my local resource file containing embedded "tags" e.g. resource name "Help" contains:
We suggest you read our [HelpPage]
before proceeding
and then my .RenderWithTags() method will expand the tags but dynamically executing the code in the dictionary passed e.g. replace [HelpPage] with whatever #Html.ActionLink(...) produces.
I know I can use Microsoft.CSharp.CSharpCodeProvider().CreateCompiler() to compile C# code on the fly, but what about Razor code?
This will be rather difficult to do.
Instead, you should put delegates in your dictionary.
For example:
new Dictionary<string, Func<string>>() {
{ "HelpPage", () => Html.ActionLink(...).ToString() }
}
If you're creating the dictionary in a Razor page, you could also use inline helpers:
new Dictionary<string, Func<Something, HelperResult>>() {
{ "HelpPage", #Html.ActionLink(...) }
}
This will allow to use arbitrary Razor markup in the values.
However, I would probably recommend that you create a single global dictionary in code, so that you don't need to repeat definitions across pages. (Depending on how you use it)
in the end, the solution turned out pretty slick. would not have been possible without SLaks, I'm much obliged for the help (though I didn't end up using inline helpers (but thanks for the intro (they're very cool))).
Now my page contains this:
#{
Dictionary<string, MvcHtmlString> tokenMap = new Dictionary<string, MvcHtmlString>() {
{"HelpPage", Html.ActionLink("help page", "Help", "Home") }
};
}
and somewhere below I have:
#this.Resource("Epilogue", tokenMap)
To accomplish this simplicity:
public static class PageExtensions
{
public static MvcHtmlString Resource(this WebViewPage page, string key)
{
HttpContextBase http = page.ViewContext.HttpContext;
string ret = (string) http.GetLocalResourceObject(page.VirtualPath, key);
return MvcHtmlString.Create(ret);
}
public static MvcHtmlString Resource(
this WebViewPage page, string key,
Dictionary<string, MvcHtmlString> tokenMap
) {
HttpContextBase http = page.ViewContext.HttpContext;
string text = (string) http.GetLocalResourceObject(page.VirtualPath, key);
return new TagReplacer(text, tokenMap).ToMvcHtmlString();
}
}
...and:
public class TagReplacer
{
Dictionary<string, MvcHtmlString> tokenmap;
public string Value { get; set; }
public TagReplacer(string text, Dictionary<string, MvcHtmlString> tokenMap)
{
tokenmap = tokenMap;
Regex re = new Regex(#"\[.*?\]", RegexOptions.IgnoreCase);
Value = re.Replace(text, new MatchEvaluator(this.Replacer));
}
public string Replacer(Match m)
{
return tokenmap[m.Value.RemoveSet("[]")].ToString();
}
public MvcHtmlString ToMvcHtmlString()
{
return MvcHtmlString.Create(Value);
}
}
...with a little extra help:
public static class ObjectExtensions
{
public static string ReplaceSet(this string text, string set, string x)
{
for (int i = 0; i < set.Length; i++)
{
text = text.Replace(set[i].ToString(), x);
}
return text;
}
public static string RemoveSet(this string text, string set)
{
return text.ReplaceSet(set, "");
}
}
comments or feedback on how it could have been better most welcome!
I was wondering if anyone knows if it possible to use any of the "out of the box" ASP.NET MVC3 helpers to generate a "link button"...I currently use following:
<a class="button" title="My Action" href="#Url.Action("MyAction", "MyController", new { id = item.Id })">
<img alt="My Action" src="#Url.Content("~/Content/Images/MyLinkImage.png")" />
</a>
I am trying to avoid using MvcFutures, but even if I was able to use them, I don't think there is a extension method it there that will accomplish this either. (I believe solution in this case would be to roll custom helper as seen here)
Finally, this post also has a good idea to handle this via CSS, but that is not what I am asking...
I am using the following to generate action links:
using System;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
using Fasterflect;
namespace StackOverflow.Mvc.Extensions
{
public static class HtmlExtensions
{
#region ActionImage
// href image link
public static string ActionImage( this HtmlHelper helper, string href, string linkText, object htmlAttributes,
string alternateText, string imageSrc, object imageAttributes )
{
var sb = new StringBuilder();
const string format = "<a href=\"{0}\"{1}>{2}</a>";
string image = helper.Image( imageSrc, alternateText, imageAttributes ).ToString();
string content = string.IsNullOrWhiteSpace( linkText ) ? image : image + linkText;
sb.AppendFormat( format, href, GetAttributeString( htmlAttributes ), content );
return sb.ToString();
}
// controller/action image link
public static string ActionImage( this HtmlHelper helper, string controller, string action, string linkText, object htmlAttributes,
string alternateText, string imageSrc, object imageAttributes )
{
bool isDefaultAction = string.IsNullOrEmpty( action ) || action == "Index";
string href = "/" + (controller ?? "Home") + (isDefaultAction ? string.Empty : "/" + action);
return ActionImage( helper, href, linkText, htmlAttributes, alternateText, imageSrc, imageAttributes );
}
// T4MVC ActionResult image link
public static string ActionImage( this HtmlHelper helper, ActionResult actionResult, string linkText, object htmlAttributes,
string alternateText, string imageSrc, object imageAttributes )
{
var controller = (string) actionResult.GetPropertyValue( "Controller" );
var action = (string) actionResult.GetPropertyValue( "Action" );
return ActionImage( helper, controller, action, linkText, htmlAttributes, alternateText, imageSrc, imageAttributes );
}
#endregion
#region Helpers
private static string GetAttributeString( object htmlAttributes )
{
if( htmlAttributes == null )
{
return string.Empty;
}
const string format = " {0}=\"{1}\"";
var sb = new StringBuilder();
htmlAttributes.GetType().Properties().ForEach( p => sb.AppendFormat( format, p.Name, p.Get( htmlAttributes ) ) );
return sb.ToString();
}
#endregion
}
}
Note that the GetAttributeString method relies on the Fasterflect library to make reflection tasks easier, but you can replace that with regular reflection if you prefer not to take the additional dependency.
The Image helper extension used to be part of MvcContrib but appears to have been removed, most likely because the functionality is now built in to MVC. Regardless, I've included it below for completeness:
public static class ImageExtensions {
public static MvcHtmlString Image(this HtmlHelper helper, string imageRelativeUrl, string alt, object htmlAttributes) {
return Image(helper, imageRelativeUrl, alt, new RouteValueDictionary(htmlAttributes));
}
public static MvcHtmlString Image(this HtmlHelper helper, string imageRelativeUrl, string alt, IDictionary<string, object> htmlAttributes) {
if (String.IsNullOrEmpty(imageRelativeUrl)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "imageRelativeUrl");
}
string imageUrl = UrlHelper.GenerateContentUrl(imageRelativeUrl, helper.ViewContext.HttpContext);
return MvcHtmlString.Create(Image(imageUrl, alt, htmlAttributes).ToString(TagRenderMode.SelfClosing));
}
public static TagBuilder Image(string imageUrl, string alt, IDictionary<string, object> htmlAttributes) {
if (String.IsNullOrEmpty(imageUrl)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "imageUrl");
}
TagBuilder imageTag = new TagBuilder("img");
if (!String.IsNullOrEmpty(imageUrl)) {
imageTag.MergeAttribute("src", imageUrl);
}
if (!String.IsNullOrEmpty(alt)) {
imageTag.MergeAttribute("alt", alt);
}
imageTag.MergeAttributes(htmlAttributes, true);
if (imageTag.Attributes.ContainsKey("alt") && !imageTag.Attributes.ContainsKey("title")) {
imageTag.MergeAttribute("title", (imageTag.Attributes["alt"] ?? "").ToString());
}
return imageTag;
}
}
The snippet you have looks quite good. You should wrap it in a general-purpose html helper and call it a day. I'm sure there are other more interesting aspects to your application than nit picking about UI helpers :)
Check at the bottom of this blog post for an example with HTML extension methods from Stephen Walther