Format Sitecore Date using Sitecore().Field()? - c#

I need to use a custom format for a date (i.e. dddd dd MMMM yyyy). Is it possible to pass this format to Sitecore().Field()? I would like to do something like this:
#Html.Sitecore().Field("Day1", new { #format="dddd dd MMMM yyyy"})
However, after some Googling, I found that I either have to create a custom field helper to do this or a custom model. Is there really no way to do this using base Sitecore? It's important this be done through Sitecore().Field() as I need the content editor to be able to edit the value.
We're on Sitecore 7.5

As far I know Sitecore doesn't have such a functionality out of the box.
You can use a helper for this functionality, please check below code.
I used this code and is working fine. You can edit date field also from page editor because the field is edited through Sitecore pipelines.
public static class Helper
{
public static HtmlString RenderField(
this SC.Mvc.Helpers.SitecoreHelper sitecoreHelper,
string fieldNameOrId,
bool disableWebEdit = false,
SC.Collections.SafeDictionary<string> parameters = null)
{
if (parameters == null)
{
parameters = new SC.Collections.SafeDictionary<string>();
}
return sitecoreHelper.Field(
fieldNameOrId,
new
{
DisableWebEdit = disableWebEdit,
Parameters = parameters
});
}
public static HtmlString RenderField(
this SC.Mvc.Helpers.SitecoreHelper sitecoreHelper,
SC.Data.ID fieldId,
bool disableWebEdit = false,
SC.Collections.SafeDictionary<string> parameters = null)
{
return RenderField(
sitecoreHelper,
fieldId.ToString(),
disableWebEdit,
parameters);
}
public static HtmlString RenderDate(
this SC.Mvc.Helpers.SitecoreHelper sitecoreHelper,
string fieldNameOrId,
string format = "D",
bool disableWebEdit = false,
bool setCulture = true,
SC.Collections.SafeDictionary<string> parameters = null)
{
if (setCulture)
{
Thread.CurrentThread.CurrentUICulture =
new CultureInfo(SC.Context.Language.Name);
Thread.CurrentThread.CurrentCulture =
CultureInfo.CreateSpecificCulture(SC.Context.Language.Name);
}
if (parameters == null)
{
parameters = new SC.Collections.SafeDictionary<string>();
}
parameters["format"] = format;
return RenderField(
sitecoreHelper,
fieldNameOrId,
disableWebEdit,
parameters);
}
public static HtmlString RenderDate(
this SC.Mvc.Helpers.SitecoreHelper sitecoreHelper,
SC.Data.ID fieldId,
string format = "D",
bool disableWebEdit = false,
bool setCulture = true,
SC.Collections.SafeDictionary<string> parameters = null)
{
return RenderDate(
sitecoreHelper,
fieldId.ToString(),
format,
disableWebEdit,
setCulture,
parameters);
}
public static HtmlString TagField(
this SC.Mvc.Helpers.SitecoreHelper sitecoreHelper,
string fieldNameOrId,
string htmlElement,
bool disableWebEdit = false,
SC.Collections.SafeDictionary<string> parameters = null)
{
SC.Data.Items.Item item =
SC.Mvc.Presentation.RenderingContext.Current.ContextItem;
if (item == null || String.IsNullOrEmpty(item[fieldNameOrId]))
{
return new HtmlString(String.Empty);
}
string value = sitecoreHelper.RenderField(
fieldNameOrId,
disableWebEdit,
parameters).ToString();
return new HtmlString(String.Format(
"<{0}>{1}</{0}>",
htmlElement,
value));
}
public static HtmlString TagField(
this SC.Mvc.Helpers.SitecoreHelper sitecoreHelper,
SC.Data.ID fieldId,
string htmlElement,
bool disableWebEdit = false,
SC.Collections.SafeDictionary<string> parameters = null)
{
return TagField(
sitecoreHelper,
fieldId.ToString(),
htmlElement,
disableWebEdit,
parameters);
}
}
In your cshtml you will have:
#Html.Sitecore().RenderDate("Name of field or id", "your format")
John West write about how to extend sitecore helpers here:
http://www.sitecore.net/learn/blogs/technical-blogs/john-west-sitecore-blog/posts/2012/06/sitecore-mvc-playground-part-4-extending-the-sitecorehelper-class.aspx

You can format date using below Field render syntax which is easy and out of the box.
#Html.Sitecore().Field("Date Field", new {format="MMM dd, yyyy"})
That's it. The format value leveraged the standard date format specifications.

For those who had an issue while dealing with spaces, simply replace all the spaces with "\n".
An example here:
#Html.Sitecore().Field(datefield, new {format="MMM\ndd,\nyyyy"})

I have modified #SitecoreClimber answer, because it doesn't work for rendering children items since it uses default RenderingContext.Current
So I have updated every method arguments and added Item item and used that item for base field rendering, like this:
public static HtmlString RenderField(this SitecoreHelper sitecoreHelper, string fieldNameOrId, Item item, bool disableWebEdit = false, SafeDictionary<string> parameters = null)
{
if (parameters == null)
{
parameters = new SafeDictionary<string>();
}
return sitecoreHelper.Field(fieldNameOrId, item,
new
{
DisableWebEdit = disableWebEdit,
Parameters = parameters
});
}
So in my MVC view I can now have:
#foreach (Item item in #Model.Item.Children)
{
<div class="event-date">
#Html.Sitecore().RenderDate("Date", item, "d MMM")
</div>
}

Related

Dynamic string interpolation

Can anyone help me with this?
Required Output: "Todo job for admin"
class Program
{
static void Main(string[] args)
{
Console.WriteLine(ReplaceMacro("{job.Name} job for admin", new Job { Id = 1, Name = "Todo", Description="Nothing" }));
Console.ReadLine();
}
static string ReplaceMacro(string value, Job job)
{
return value; //Output should be "Todo job for admin"
}
}
class Job
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Two suggestions:
DataBinder.Eval
string ReplaceMacro(string value, Job job)
{
return Regex.Replace(value, #"{(?<exp>[^}]+)}", match => {
return (System.Web.UI.DataBinder.Eval(new { Job = job }, match.Groups["exp"].Value) ?? "").ToString();
});
}
Linq.Expression
Use the Dynamic Query class provided in the MSDN LINQSamples:
string ReplaceMacro(string value, Job job)
{
return Regex.Replace(value, #"{(?<exp>[^}]+)}", match => {
var p = Expression.Parameter(typeof(Job), "job");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, match.Groups["exp"].Value);
return (e.Compile().DynamicInvoke(job) ?? "").ToString();
});
}
In my opinion, the Linq.Expression is more powerful, so if you trust the input string, you can do more interesting things, i.e.:
value = "{job.Name.ToUpper()} job for admin"
return = "TODO job for admin"
You can't use string interpolation this way. But you can still use the pre-C#6 way to do it using string.Format:
static void Main(string[] args)
{
Console.WriteLine(ReplaceMacro("{0} job for admin", new Job { Id = 1, Name = "Todo", Description = "Nothing" }));
Console.ReadLine();
}
static string ReplaceMacro(string value, Job job)
{
return string.Format(value, job.Name);
}
This generic solution Extend the answer provided by #Dan
It can be used for any typed object.
install System.Linq.Dynamic
Install-Package System.Linq.Dynamic -Version 1.0.7
string ReplaceMacro(string value, object #object)
{
return Regex.Replace(value, #"{(.+?)}",
match => {
var p = Expression.Parameter(#object.GetType(), #object.GetType().Name);
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, match.Groups[1].Value);
return (e.Compile().DynamicInvoke(#object) ?? "").ToString();
});
}
See a working demo for a Customer type
You could use RazorEngine:
using RazorEngine;
class Program
{
static void Main(string[] args)
{
Console.WriteLine(ReplaceMacro("#Model.Name job for admin", new Job { Id = 1, Name = "Todo", Description="Nothing" }));
Console.ReadLine();
}
static string ReplaceMacro(string value, Job job)
{
return Engine.Razor.RunCompile(value, "key", typeof(Job), job);
}
}
It even supports Anonymous Types and method calls:
string template = "Hello #Model.Name. Today is #Model.Date.ToString(\"MM/dd/yyyy\")";
var model = new { Name = "Matt", Date = DateTime.Now };
string result = Engine.Razor.RunCompile(template, "key", null, model);
Little late to the party! Here is the one I wrote -
using System.Reflection;
using System.Text.RegularExpressions;
public static class AmitFormat
{
//Regex to match keywords of the format {variable}
private static readonly Regex TextTemplateRegEx = new Regex(#"{(?<prop>\w+)}", RegexOptions.Compiled);
/// <summary>
/// Replaces all the items in the template string with format "{variable}" using the value from the data
/// </summary>
/// <param name="templateString">string template</param>
/// <param name="model">The data to fill into the template</param>
/// <returns></returns>
public static string FormatTemplate(this string templateString, object model)
{
if (model == null)
{
return templateString;
}
PropertyInfo[] properties = model.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (!properties.Any())
{
return templateString;
}
return TextTemplateRegEx.Replace(
templateString,
match =>
{
PropertyInfo property = properties.FirstOrDefault(propertyInfo =>
propertyInfo.Name.Equals(match.Groups["prop"].Value, StringComparison.OrdinalIgnoreCase));
if (property == null)
{
return string.Empty;
}
object value = property.GetValue(model, null);
return value == null ? string.Empty : value.ToString();
});
}
}
Example -
string format = "{foo} is a {bar} is a {baz} is a {qux} is a really big {fizzle}";
var data = new { foo = 123, bar = true, baz = "this is a test", qux = 123.45, fizzle = DateTime.UtcNow };
Compared with other implementations given by Phil Haack and here are the results for the above example -
AmitFormat took 0.03732 ms
Hanselformat took 0.09482 ms
OskarFormat took 0.1294 ms
JamesFormat took 0.07936 ms
HenriFormat took 0.05024 ms
HaackFormat took 0.05914 ms
Wrap the string in a function...
var f = x => $"Hi {x}";
f("Mum!");
//... Hi Mum!
You need named string format replacement. See Phil Haack's post from years ago: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx/
Not exactly but with bit tweek, I have created generic interpolation which support fields / property only.
public static string Interpolate(this string template, params Expression<Func<object, string>>[] values)
{
string result = template;
values.ToList().ForEach(x =>
{
MemberExpression member = x.Body as MemberExpression;
string oldValue = $"{{{member.Member.Name}}}";
string newValue = x.Compile().Invoke(null).ToString();
result = result.Replace(oldValue, newValue);
}
);
return result;
}
Test case
string jobStr = "{Name} job for admin";
var d = new { Id = 1, Name = "Todo", Description = "Nothing" };
var result = jobStr.Interpolate(x => d.Name);
Another
string sourceString = "I wanted abc as {abc} and {dateTime} and {now}";
var abc = "abcIsABC";
var dateTime = DateTime.Now.Ticks.ToString();
var now = DateTime.Now.ToString();
string result = sourceString.Interpolate(x => abc, x => dateTime, x => now);
Starting from the accepted answer I created a generic extension method:
public static string Replace<T>(this string template, T value)
{
return Regex.Replace(template, #"{(?<exp>[^}]+)}", match => {
var p = Expression.Parameter(typeof(T), typeof(T).Name);
var e = System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda(new[] { p }, null, match.Groups["exp"].Value);
return (e.Compile().DynamicInvoke(value) ?? "").ToString();
});
}
Answer from #ThePerplexedOne is better, but if you really need to avoid string interpolation, so
static string ReplaceMacro(string value, Job job)
{
return value?.Replace("{job.Name}", job.Name); //Output should be "Todo job for admin"
}
You should change your function to:
static string ReplaceMacro(Job obj, Func<dynamic, string> function)
{
return function(obj);
}
And call it:
Console.WriteLine(
ReplaceMacro(
new Job { Id = 1, Name = "Todo", Description = "Nothing" },
x => $"{x.Name} job for admin"));
If you really need this, you can do it using Roslyn, create string – class implementation like
var stringToInterpolate = "$#\"{{job.Name}} job for admin\"";
var sourceCode = $#"
using System;
class RuntimeInterpolation(Job job)
{{
public static string Interpolate() =>
{stringToInterpolate};
}}";
then make an assembly
var assembly = CompileSourceRoslyn(sourceCode);
var type = assembly.GetType("RuntimeInterpolation");
var instance = Activator.CreateInstance(type);
var result = (string) type.InvokeMember("Interpolate",
BindingFlags.Default | BindingFlags.InvokeMethod, null, instance, new object[] {new Job { Id = 1, Name = "Todo", Description="Nothing" }});
Console.WriteLine(result);
you'll get this code by runtime and you'll get your result (also you'll need to attach link to your job class to that assembly)
using System;
class RuntimeInterpolation(Job job)
{
public static string Interpolate() =>
$#"{job.Name} job for admin";
}
You can read about how to implement CompileSourceRoslyn here Roslyn - compiling simple class: "The type or namespace name 'string' could not be found..."
Wondering no one has mentioned mustache-sharp. Downloadable via Nuget.
string templateFromSomewhere = "url: {{Url}}, Name:{{Name}}";
FormatCompiler compiler = new FormatCompiler();
Generator generator = compiler.Compile(templateFromSomewhere);
string result = generator.Render(new
{
Url="https://google.com",
Name = "Bob",
});//"url: https://google.com, Name:Bob"
More examples could be found here, at the unit testing file.
The implementation I'd come up with very similar to giganoide's allowing callers to substitute the name used in the template for the data to interpolate from. This can accommodate things like feeding it anonymous types. It will used the provided interpolateDataAs name if provided, otherwise if it isn't an anonymous type it will default to the type name, otherwise it expects "data". (or specifically the name of the property) It's written as an injectable dependency but should still work as an extension method.
public interface ITemplateInterpolator
{
/// <summary>
/// Attempt to interpolate the provided template string using
/// the data provided.
/// </summary>
/// <remarks>
/// Templates may want to use a meaninful interpolation name
/// like "enquiry.FieldName" or "employee.FieldName" rather than
/// "data.FieldName". Use the interpolateDataAs to pass "enquiry"
/// for example to substitute the default "data" prefix.
/// </remarks>
string? Interpolate<TData>(string template, TData data, string? interpolateDataAs = null) where TData : class;
}
public class TemplateInterpolator : ITemplateInterpolator
{
/// <summary>
/// <see cref="ITemplateInterpolator.Interpolate(string, dynamic, string)"/>
/// </summary>
string? ITemplateInterpolator.Interpolate<TData>(string template, TData data, string? interpolateDataAs)
{
if (string.IsNullOrEmpty(template))
return template;
if (string.IsNullOrEmpty(interpolateDataAs))
{
interpolateDataAs = !typeof(TData).IsAnonymousType() ? typeof(TData).Name : nameof(data);
}
var parsed = Regex.Replace(template, #"{(?<exp>[^}]+)}", match =>
{
var param = Expression.Parameter(typeof(TData), interpolateDataAs);
var e = System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda(new[] { param }, null, match.Groups["exp"].Value);
return (e.Compile().DynamicInvoke(data) ?? string.Empty).ToString();
});
return parsed;
}
For detecting the anonymous type:
public static bool IsAnonymousType(this Type type)
{
if (type.IsGenericType)
{
var definition = type.GetGenericTypeDefinition();
if (definition.IsClass && definition.IsSealed && definition.Attributes.HasFlag(TypeAttributes.NotPublic))
{
var attributes = definition.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false);
return (attributes != null && attributes.Length > 0);
}
}
return false;
}
Test Suite:
[TestFixture]
public class WhenInterpolatingATemplateString
{
[TestCase("")]
[TestCase(null)]
public void ThenEmptyValueReturedWhenNoTemplateProvided(string? template)
{
ITemplateInterpolator testInterpolator = new TemplateInterpolator();
var testData = new TestData { Id = 14, Name = "Test" };
var result = testInterpolator.Interpolate(template!, testData);
Assert.That(result, Is.EqualTo(template));
}
[Test]
public void ThenTheTypeNameIsUsedForTheDataReferenceForDefinedClasses()
{
ITemplateInterpolator testInterpolator = new TemplateInterpolator();
var testData = new TestData { Id = 14, Name = "Test" };
string template = "This is a record named \"{TestData.Name}\" with an Id of {testdata.Id}."; // case insensitive.
string expected = "This is a record named \"Test\" with an Id of 14.";
var result = testInterpolator.Interpolate(template, testData);
Assert.That(result, Is.EqualTo(expected));
}
[Test]
public void ThenTheDefaultNameIsUsedForTheDataReferenceForAnonymous()
{
ITemplateInterpolator testInterpolator = new TemplateInterpolator();
var testData = new { Id = 14, Name = "Test" };
string template = "This is a record named \"{data.Name}\" with an Id of {data.Id}.";
string expected = "This is a record named \"Test\" with an Id of 14.";
var result = testInterpolator.Interpolate(template, testData);
Assert.That(result, Is.EqualTo(expected));
}
[Test]
public void ThenTheProvidedDataReferenceNameOverridesTheTypeName()
{
ITemplateInterpolator testInterpolator = new TemplateInterpolator();
var testData = new TestData { Id = 14, Name = "Test" };
string template = "This is a record named \"{otherData.Name}\" with an Id of {otherData.Id}.";
string expected = "This is a record named \"Test\" with an Id of 14.";
var result = testInterpolator.Interpolate(template, testData, "otherData");
Assert.That(result, Is.EqualTo(expected));
}
[Test]
public void ThenExceptionIsThrownWhenTemplateReferencesUnknownDataValues()
{
ITemplateInterpolator testInterpolator = new TemplateInterpolator();
var testData = new TestData { Id = 14, Name = "Test" };
string template = "This is a record named \"{testData.Name}\" with an Id of {testData.Id}. {testData.ExtraDetails}";
Assert.Throws<ParseException>(() => { var result = testInterpolator.Interpolate(template, testData, "testData"); });
}
[Test]
public void ThenDataFormattingExpressionsAreApplied()
{
ITemplateInterpolator testInterpolator = new TemplateInterpolator();
var testData = new { Id = 14, Name = "Test", IsActive = true, EffectiveDate = DateTime.Today };
string template = "The active state is {data.IsActive?\"Yes\":\"No\"}, Effective {data.EffectiveDate.ToString(\"yyyy-MM-dd\")}";
string expected = "The active state is Yes, Effective " + DateTime.Today.ToString("yyyy-MM-dd");
var result = testInterpolator.Interpolate(template, testData);
Assert.That(result, Is.EqualTo(expected));
}
private class TestData
{
public int Id { get; set; }
public string Name { get; set; }
}
}
I really don't understand the point of your ReplaceMacro method...
But here's how it should work:
class Program
{
static void Main(string[] args)
{
var job = new Job { Id = 1, Name = "Todo", Description = "Nothing" };
Console.WriteLine($"{job.Name} job for admin");
Console.ReadLine();
}
}
But if you really want the dynamic feel to it, your ReplaceMacro method should just take one parameter, which is the job:
static string ReplaceMacro(Job job)
{
return $"{job.Name} job for admin.";
}
And use it like:
var job = new Job { Id = 1, Name = "Todo", Description = "Nothing" };
Console.WriteLine(ReplaceMacro(job));
Or something to that effect.

how to redirect to different view when we click on page numbers in mvc asp.net

i have implemented one web grid with pagination enabled using webgridextension.cs file. Same page have multiple buttons to perform different actions like search, get excel, get pdf. in order to perform different form submits i used multiplebutton with action selector attribute. when i click on page number down to the grid it was navigating to default action index.
Here i want to navigate to different action GetRFQData with model (already loaded) in the page..
can you please help me on this.
WebGrid grid = new WebGrid(null, rowsPerPage: 25, canPage: true, defaultSort: "RFQID");
grid.Bind(Model != null ? Model.RFQSearchResults != null ? Model.RFQSearchResults
: new List<Shipsurance.Model.RFQ>()
: new List<Shipsurance.Model.RFQ>(), rowCount: Model != null ? Model.TotalRowsCount :25, autoSortAndPage: false);
[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;
}
}
[MultipleButton(Name = "action", Argument = "GetRFQData")]
public ActionResult GetData(Shipsurance.Model.RFQ rfqModelData)
{
SerachRFQ getRFQDetails = new SerachRFQ();
return View("Index", getDetails.getResults(rfqModelData));
}
You can use RedirectToAction() method.
return RedirectToAction("Your action", model);

MaxJsonLength Error For Large Data [duplicate]

In one of my controller actions I am returning a very large JsonResult to fill a grid.
I am getting the following InvalidOperationException exception:
Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property.
Setting the maxJsonLength property in the web.config to a higher value unfortunately does not show any effect.
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization maxJsonLength="2147483644"/>
</webServices>
</scripting>
</system.web.extensions>
I don't want to pass it back as a string as mentioned in this SO answer.
In my research I came across this blog post where writing an own ActionResult (e.g. LargeJsonResult : JsonResult) is recommended to bypass this behaviour.
Is this then the only solution?
Is this a bug in ASP.NET MVC?
Am I missing something?
Any help would be most appreciated.
It appears this has been fixed in MVC4.
You can do this, which worked well for me:
public ActionResult SomeControllerAction()
{
var jsonResult = Json(veryLargeCollection, JsonRequestBehavior.AllowGet);
jsonResult.MaxJsonLength = int.MaxValue;
return jsonResult;
}
You could also use ContentResult as suggested here instead of subclassing JsonResult.
var serializer = new JavaScriptSerializer { MaxJsonLength = Int32.MaxValue, RecursionLimit = 100 };
return new ContentResult()
{
Content = serializer.Serialize(data),
ContentType = "application/json",
};
Unfortunately the web.config setting is ignored by the default JsonResult implementation. So I guess you will need to implement a custom json result to overcome this issue.
No need for a custom class. This is all that is needed:
return new JsonResult { Data = Result, MaxJsonLength = Int32.MaxValue };
where Result is that data you wish to serialize.
I'm surprised no one has suggested using a result filter. This is the cleanest way to globally hook into the action/result pipeline:
public class JsonResultFilter : IResultFilter
{
public int? MaxJsonLength { get; set; }
public int? RecursionLimit { get; set; }
public void OnResultExecuting(ResultExecutingContext filterContext)
{
if (filterContext.Result is JsonResult jsonResult)
{
// override properties only if they're not set
jsonResult.MaxJsonLength = jsonResult.MaxJsonLength ?? MaxJsonLength;
jsonResult.RecursionLimit = jsonResult.RecursionLimit ?? RecursionLimit;
}
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
}
}
Then, register an instance of that class using GlobalFilters.Filters:
GlobalFilters.Filters.Add(new JsonResultFilter { MaxJsonLength = int.MaxValue });
If use Json.NET to generate the json string, it doesn't need to set MaxJsonLength value.
return new ContentResult()
{
Content = Newtonsoft.Json.JsonConvert.SerializeObject(data),
ContentType = "application/json",
};
Alternative ASP.NET MVC 5 Fix:
In my case the error was occurring during the request. Best approach in my scenario is modifying the actual JsonValueProviderFactory which applies the fix to the global project and can be done by editing the global.cs file as such.
JsonValueProviderConfig.Config(ValueProviderFactories.Factories);
add a web.config entry:
<add key="aspnet:MaxJsonLength" value="20971520" />
and then create the two following classes
public class JsonValueProviderConfig
{
public static void Config(ValueProviderFactoryCollection factories)
{
var jsonProviderFactory = factories.OfType<JsonValueProviderFactory>().Single();
factories.Remove(jsonProviderFactory);
factories.Add(new CustomJsonValueProviderFactory());
}
}
This is basically an exact copy of the default implementation found in System.Web.Mvc but with the addition of a configurable web.config appsetting value aspnet:MaxJsonLength.
public class CustomJsonValueProviderFactory : ValueProviderFactory
{
/// <summary>Returns a JSON value-provider object for the specified controller context.</summary>
/// <returns>A JSON value-provider object for the specified controller context.</returns>
/// <param name="controllerContext">The controller context.</param>
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");
object deserializedObject = CustomJsonValueProviderFactory.GetDeserializedObject(controllerContext);
if (deserializedObject == null)
return null;
Dictionary<string, object> strs = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
CustomJsonValueProviderFactory.AddToBackingStore(new CustomJsonValueProviderFactory.EntryLimitedDictionary(strs), string.Empty, deserializedObject);
return new DictionaryValueProvider<object>(strs, CultureInfo.CurrentCulture);
}
private static object GetDeserializedObject(ControllerContext controllerContext)
{
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
return null;
string fullStreamString = (new StreamReader(controllerContext.HttpContext.Request.InputStream)).ReadToEnd();
if (string.IsNullOrEmpty(fullStreamString))
return null;
var serializer = new JavaScriptSerializer()
{
MaxJsonLength = CustomJsonValueProviderFactory.GetMaxJsonLength()
};
return serializer.DeserializeObject(fullStreamString);
}
private static void AddToBackingStore(EntryLimitedDictionary backingStore, string prefix, object value)
{
IDictionary<string, object> strs = value as IDictionary<string, object>;
if (strs != null)
{
foreach (KeyValuePair<string, object> keyValuePair in strs)
CustomJsonValueProviderFactory.AddToBackingStore(backingStore, CustomJsonValueProviderFactory.MakePropertyKey(prefix, keyValuePair.Key), keyValuePair.Value);
return;
}
IList lists = value as IList;
if (lists == null)
{
backingStore.Add(prefix, value);
return;
}
for (int i = 0; i < lists.Count; i++)
{
CustomJsonValueProviderFactory.AddToBackingStore(backingStore, CustomJsonValueProviderFactory.MakeArrayKey(prefix, i), lists[i]);
}
}
private class EntryLimitedDictionary
{
private static int _maximumDepth;
private readonly IDictionary<string, object> _innerDictionary;
private int _itemCount;
static EntryLimitedDictionary()
{
_maximumDepth = CustomJsonValueProviderFactory.GetMaximumDepth();
}
public EntryLimitedDictionary(IDictionary<string, object> innerDictionary)
{
this._innerDictionary = innerDictionary;
}
public void Add(string key, object value)
{
int num = this._itemCount + 1;
this._itemCount = num;
if (num > _maximumDepth)
{
throw new InvalidOperationException("The length of the string exceeds the value set on the maxJsonLength property.");
}
this._innerDictionary.Add(key, value);
}
}
private static string MakeArrayKey(string prefix, int index)
{
return string.Concat(prefix, "[", index.ToString(CultureInfo.InvariantCulture), "]");
}
private static string MakePropertyKey(string prefix, string propertyName)
{
if (string.IsNullOrEmpty(prefix))
{
return propertyName;
}
return string.Concat(prefix, ".", propertyName);
}
private static int GetMaximumDepth()
{
int num;
NameValueCollection appSettings = ConfigurationManager.AppSettings;
if (appSettings != null)
{
string[] values = appSettings.GetValues("aspnet:MaxJsonDeserializerMembers");
if (values != null && values.Length != 0 && int.TryParse(values[0], out num))
{
return num;
}
}
return 1000;
}
private static int GetMaxJsonLength()
{
int num;
NameValueCollection appSettings = ConfigurationManager.AppSettings;
if (appSettings != null)
{
string[] values = appSettings.GetValues("aspnet:MaxJsonLength");
if (values != null && values.Length != 0 && int.TryParse(values[0], out num))
{
return num;
}
}
return 1000;
}
}
I solved the issue by following this link
namespace System.Web.Mvc
{
public sealed class JsonDotNetValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
return null;
var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
var bodyText = reader.ReadToEnd();
return String.IsNullOrEmpty(bodyText) ? null : new DictionaryValueProvider<object>(JsonConvert.DeserializeObject<ExpandoObject>(bodyText, new ExpandoObjectConverter()), CultureInfo.CurrentCulture);
}
}
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
//Remove and JsonValueProviderFactory and add JsonDotNetValueProviderFactory
ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault());
ValueProviderFactories.Factories.Add(new JsonDotNetValueProviderFactory());
}
there is a bit other case - data is sent from client to server.
when you are using controller method and model is huge :
[HttpPost]
public ActionResult AddOrUpdateConsumerFile(FileMetaDataModelView inputModel)
{
if (inputModel == null) return null;
....
}
system throws exception like this "Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property. Parameter name: input"
Only changing Web.config settings is not enough to help in this case. You could additionally override mvc json serializer for supporting huge data model sizes or manually deserialize model from Request. Your controller method becomes:
[HttpPost]
public ActionResult AddOrUpdateConsumerFile()
{
FileMetaDataModelView inputModel = RequestManager.GetModelFromJsonRequest<FileMetaDataModelView>(HttpContext.Request);
if (inputModel == null) return null;
......
}
public static T GetModelFromJsonRequest<T>(HttpRequestBase request)
{
string result = "";
using (Stream req = request.InputStream)
{
req.Seek(0, System.IO.SeekOrigin.Begin);
result = new StreamReader(req).ReadToEnd();
}
return JsonConvert.DeserializeObject<T>(result);
}
You can try define in your LINQ expression only the field's that you will need.
Example. Imagine that you have an Model with Id, Name, Phone and Picture (byte array) and need to load from json into an select list.
LINQ Query:
var listItems = (from u in Users where u.name.Contains(term) select u).ToList();
The problem here is "select u" that get all fields. So, if you have big pictures, booomm.
How to solve? very, very simple.
var listItems = (from u in Users where u.name.Contains(term) select new {u.Id, u.Name}).ToList();
The best practices is select only the field that you will use.
Remember. This is a simple tip, but can help many ASP.NET MVC developpers.
protected override JsonResult Json(object data, string contentType, System.Text.Encoding contentEncoding, JsonRequestBehavior behavior)
{
return new JsonResult()
{
Data = data,
ContentType = contentType,
ContentEncoding = contentEncoding,
JsonRequestBehavior = behavior,
MaxJsonLength = Int32.MaxValue
};
}
Was the fix for me in MVC 4.
None of the above worked out for me until I changed the Action as [HttpPost].
and made the ajax type as POST.
[HttpPost]
public JsonResult GetSelectedSignalData(string signal1,...)
{
JsonResult result = new JsonResult();
var signalData = GetTheData();
try
{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer { MaxJsonLength = Int32.MaxValue, RecursionLimit = 100 };
result.Data = serializer.Serialize(signalData);
return Json(result, JsonRequestBehavior.AllowGet);
..
..
...
}
And the ajax call as
$.ajax({
type: "POST",
url: some_url,
data: JSON.stringify({ signal1: signal1,.. }),
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data !== null) {
setValue();
}
},
failure: function (data) {
$('#errMessage').text("Error...");
},
error: function (data) {
$('#errMessage').text("Error...");
}
});
You need to read from the configuration section manually before your code returns a JsonResult object. Simply read from web.config in single line:
var jsonResult = Json(resultsForAjaxUI);
jsonResult.MaxJsonLength = (ConfigurationManager.GetSection("system.web.extensions/scripting/webServices/jsonSerialization") as System.Web.Configuration.ScriptingJsonSerializationSection).MaxJsonLength;
return jsonResult;
Be sure you defined configuration element in web.config
this worked for me
JsonSerializerSettings json = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var result = JsonConvert.SerializeObject(list, Formatting.Indented, json);
return new JsonResult { Data = result, MaxJsonLength = int.MaxValue };
You can put this code in cshtml if you are returning view from controller and you want to increase the length of view bag data while encoding in json in cshtml
#{
var jss = new System.Web.Script.Serialization.JavaScriptSerializer();
jss.MaxJsonLength = Int32.MaxValue;
var userInfoJson = jss.Serialize(ViewBag.ActionObj);
}
var dataJsonOnActionGrid1 = #Html.Raw(userInfoJson);
Now, dataJsonOnActionGrid1 will be accesible on js page and you will get proper result.
Thanks

MVC 4: Custom Route & Html.Action out of synch

So I have this custom route, which sets up the route table based on culture in the URL, but when I call Url.Action(...), it does not generate the localized URL. Any ideas what I'm doing wrong? The culture is changing on the page and I am able to determine what language user has selected, but Url.Action is not generating localized URL..
This is the custom route, which changes the route table values (not sure if this standard way of doing it):
public class CultureRoute : Route
{
public CultureRoute(string url, object defaults, object contraints)
: base(url, new MvcRouteHandler())
{
base.Defaults = CreateRouteValueDictionary(defaults);
base.Constraints = CreateRouteValueDictionary(contraints);
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
if (routeData != null)
{
var culture = routeData.Values["culture"].ToString();
var cookie = httpContext.Request.Cookies["culture"];
var areEqual = false;
if (cookie == null || cookie.Value == "" || !(areEqual = string.Equals(culture, cookie.Value, StringComparison.OrdinalIgnoreCase)))
{
routeData.Values["culture"] = culture;
httpContext.Response.Cookies.Add(new HttpCookie("culture", culture));
}
else if (!areEqual)
{
routeData.Values["culture"] = cookie.Value;
}
CultureHelper.SetCurrentCulture(culture);
}
return routeData;
}
private static RouteValueDictionary CreateRouteValueDictionary(object values)
{
var dictionary = values as IDictionary<string, object>;
if (dictionary != null)
{
return new RouteValueDictionary(dictionary);
}
else
{
return new RouteValueDictionary(values);
}
}
}
and this helper class to set the thread culture:
public class CultureHelper
{
public static void SetCurrentCulture(string culture)
{
var info = CultureInfo.CreateSpecificCulture(culture);
Thread.CurrentThread.CurrentCulture = info;
Thread.CurrentThread.CurrentUICulture = info;
}
public static string GetCurrentCulture(bool ignoreRouteData = false)
{
if (!ignoreRouteData)
{
var routeData = HttpContext.Current.Request.RequestContext.RouteData;
object culture;
if (routeData.Values.TryGetValue("culture", out culture))
{
return culture.ToString();
}
}
var cookie = HttpContext.Current.Request.Cookies["culture"];
if (cookie != null && cookie.Value != null)
{
return cookie.Value;
}
return GetThreadCulture();
}
public static string GetThreadCulture()
{
var culture = Thread.CurrentThread.CurrentCulture.Name;
if (culture.IndexOf('-') > -1)
{
culture = culture.Substring(0, 2);
}
return culture;
}
}
and also the RouteConfig class, which is called from the Global.asax and sets up routes using my custom route class:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("Partial", new CultureRoute(
"{culture}/{cotroller}/partial/{view}",
new { culture = "ka", controller = "home", action = "partial", view = "" },
new { culture = "(ka|en)" }));
routes.Add("Default", new CultureRoute(
"{culture}/{controller}/{action}/{id}",
new { culture = "ka", controller = "home", action = "index", id = UrlParameter.Optional },
new { culture = "(ka|en)" }));
}
}
but without this extension method, I am not able to generate culture based route i.e. Url.Action does not generate URL based on route table the custom route class creates:
public static string Action2(this UrlHelper helper, string action)
{
var culture = CultureHelper.GetThreadCulture();
return helper.Action(action, new { culture = culture });
}
For it to build the URL in ActionLink, you need to override the reverse look-up method named GetVirtualPath as well. Here is an example of how I did it (but I am inheriting RouteBase instead of Route, so yours may need to be done differently).
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
VirtualPathData result = null;
if (requestContext.RouteData.IsAreaMatch(this.area))
{
var tenant = this.appContext.CurrentTenant;
// Get all of the pages
var pages = this.routeUrlPageListFactory.GetRouteUrlPageList(tenant.Id);
IRouteUrlPageInfo page = null;
if (this.TryFindMatch(pages, values, out page))
{
result = new VirtualPathData(this, page.VirtualPath);
}
}
return result;
}
private bool TryFindMatch(IEnumerable<IRouteUrlPageInfo> pages, RouteValueDictionary values, out IRouteUrlPageInfo page)
{
page = null;
Guid contentId = Guid.Empty;
var action = Convert.ToString(values["action"]);
var controller = Convert.ToString(values["controller"]);
var localeId = (int?)values["localeId"];
if (localeId == null)
{
return false;
}
if (Guid.TryParse(Convert.ToString(values["id"]), out contentId) && action == "Index")
{
page = pages
.Where(x => x.ContentId.Equals(contentId) &&
x.ContentType.ToString().Equals(controller, StringComparison.InvariantCultureIgnoreCase))
.Where(x => x.LocaleId.Equals(localeId))
.FirstOrDefault();
if (page != null)
{
return true;
}
}
return false;
}
I found that since several of my routes need to be localized and internally I am using the CultureInfo.LCID rather than a culture string to identify culture, that it was better to put the culture parsing code in the Application_BeginRequest event in Global.asax. But that may not be necessary if you are only using the culture string internally.
BTW - I don't think that using a cookie is necessary in your case since the culture can be derived directly from the URL. It seems like unnecessary overhead, especially when you consider that cookies are transferred on every request (including images and javascript files). Not to mention the security implications of doing it this way - you should at the very least encrypt the value in the cookie. Here is an example that shows how to properly sanitize cookie data.
It was actually something else causing the incorrect behavior. I had an extension method that was generating actions to switch the language and it modified the route data.
public static string CultureRoute(this UrlHelper helper, string culture = "ka")
{
var values = helper.RequestContext.RouteData.Values;
string actionName = values["action"].ToString();
if (values.ContainsKey("culture"))
{
values["culture"] = culture;
}
else
{
values.Add("culture", culture);
}
return helper.Action(actionName, HttpContext.Current.Request.QueryString.ToRouteValues());
}
I changed it to this and it works:
public static string CultureRoute(this UrlHelper helper, string culture = "ka")
{
var values = helper.RequestContext.RouteData.Values;
string actionName = values["action"].ToString();
return helper.Action(actionName, new { culture = culture });
}
I do not need to override GetVirtualPath method to get it to work.. I think most of the time we can get away with Route class and just overriding GetRouteData, but would like to hear what others think...

Is there a way to make this validation generic?

I have a class that is an entity from a database that has a bunch of dates represented as strings. For example, it could be something like this:
public class Stuff
{
public string Date1 {get;set;}
public string Date2 {get;set;}
public string Date3 {get;set;}
public string Date4 {get;set;}
}
I then have a Validation method that validates other properties and also validates the date properties. Currently, I am validating each date separately for each object. This works, but I was wondering if there was a way I could make it generic so I didn't have to duplicate code across classes and within the class itself. I am currently doing something like:
public bool ValidateDate(string date)
{
string[] overrides = {"","__/__/____"};
bool success = true;
DateTime dateTime;
if(!overrides.Contains(date) && !DateTime.TryParse(date,out dateTime))
{
success = false;
}
return success;
}
//Notice in this method I am repeating the if statements.
public bool Validate(Stuff stuff, out string message)
{
message = string.Empty;
bool success = true;
if(!ValidateDate(stuff.Date1))
{
success = false;
message = "Date 1 is invalid";
}
if(!ValidateDate(stuff.Date2))
{
success = false;
message = "Date 2 is invalid";
}
if(!ValidateDate(stuff.Date3))
{
success = false;
message = "Date 3 is invalid";
}
if(!ValidateDate(stuff.Date4))
{
success = false;
message = "Date 4 is invalid";
}
return success;
}
void Main()
{
string message;
Stuff stuff = new Stuff();
stuff.Date1 = "01/01/2020";
stuff.Date2 = "__/__/____";
stuff.Date3 = "";
stuff.Date4 = "44/__/____";
bool valid = Validate(stuff, out message);
}
I thought about doing something like:
public bool Validate<T>(T value, out string message)
{
//Validation here
}
But, correct me if I am wrong, but this would require that I get the properties and use reflection to check the value of the date and my other problem with this is that the properties are strings, so there is no way for me to check if it is a DateTime?
I feel like I'm missing some information - right now the duplication that I see is that you are calling ValidateDate multiple times. I don't think there is a way around that - you have to call Validate on each Date property, unless (as you mentioned) you want to go the reflection route.
With reflection you would simply iterate through all properties and find any property who's name matches the pattern Date[number], you would then validate that it was indeed a DateTime (with Parse like you already do) and then move on.
If the number of fields is known and isn't too much I'd stick with your ValidateMethod though notice that message will currently only ever show you the last error (it gets overwritten each time).
You could get cute and write a method like:
public bool Validate(Stuff stuff, out string message)
{
message = "Invalid Date(s): ";
return ValidateDates(ref message, stuff.Date1, stuff.Date2, stuff.Date3, stuff.Date4);
}
public bool ValidateDate(ref string message, params string[] dates)
{
bool rv = true;
for (int i = 0; i < dates.Length; i++)
{
if (![validate DateTime as above])
{
message += i + " "; // add the failed index to the message
rv = false;
}
}
return rv;
}
The best you can do is probably something like this, although I don't advocate using string to represent dates.
public bool ValidateDates(params string[] dates)
{
return dates.All( d => ValidateDate(d));
}
public bool Validate(Stuff stuff, out string message)
{
ValidateDates(stuff.Date1,stuff.Date2,stuff.Date3);
}
You can obviously play around with this to know which date failed, for instance you can do something like
var item = dates.Select( d, i => new
{
Valid = ValidateDate(d),
Message = String.Format("Date {0} failed", i)
}).FirstOrDefault( x => !x.Valid);
if(item == null) //everything is valid
return true;
else
//set the out param
outMessageStr = item.Message;
return false;
You could make everything generic by using interfaces for the objects that you want to get dates from.
For instance:
//Defines an interface that provides a function to get multiple dates from an object.
public interface IGetDates
{
//You could use a Read-Only property too
string[] GetDates();
}
public class Stuff : IGetDate
{
//other stuff...
public string[] GetDates()
{
return new[]{ Date1, Date2, Date2, ect...};
}
}
Then your generic method would look like this:
//Uses generic constraints so only objects that use the
//IGetDates interface can call this method.
public bool Validate<T>(T stuff, out string message) where T : IGetDates
{
message = string.Empty;
bool success = true;
string[] dates = stuff.GetDates();
for(int i = 0; i < dates.Length; i++)
{
if(!Validate(dates[i]))
{
success = false;
message = string.Format("Date {0} is invalid.", i);
}
}
return success;
}

Categories

Resources