Json.Net with [WebMethod]s - json.net

I'm trying to swap out the Json formatter to Json.Net, so I can get ISO dates instead of "/Date(1379112467317)/"
I'm also letting .Net (WebForms) auto-magically handle Json serialization/deserialization through [WebMethod]s. Which don't seem to be using the Json.Net formatter.
In my global.asax, I can see the old MS Json formatter getting removed, and the new Json.net formatter added with the IsoDateTimeConverter.
But, my [Webmethod]s still come back with the old /Date()/ json strings instead of Iso dates. Do I have to do anything special in my global.asax for [Webmethod]s auto-magic to use the new formatter?
Here's the code in global:
As seen in: http://www.hanselman.com/blog/OnTheNightmareThatIsJSONDatesPlusJSONNETAndASPNETWebAPI.aspx
var formatter = config.Formatters.Where(f => { return f.SupportedMediaTypes.Any(v => v.MediaType.Equals("application/json", StringComparison.CurrentCultureIgnoreCase)); }).FirstOrDefault();
if (formatter != null)
{
config.Formatters.Remove(formatter);
}
JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new IsoDateTimeConverter());
config.Formatters.Add(new JsonNetFormatter(serializerSettings));

I think the way that you setup the formatter looks fine for me. but how do you make sure it is being use in the webform returns, it doesn't happen automatically.
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public Something GetSomething()
{
}
how about you try that.

Related

Default "Accept" header value for Asp.Net Web API

If one omits the Accept header in a request to an Asp.Net web API the server will return (415) Unsupported Media Type
I am looking for a way to force the API to assume a default return type (in my case, application/json) when the request does not contain an Accept value in its headers.
After a substantial amount of reading and searching, I'm not sure this is even possible?
You can force the framework to use XML formatter when HTTP Accept header is missing, by doing the following trick:
var jsonFormatter = config.Formatters.JsonFormatter;
config.Formatters.Remove(config.Formatters.JsonFormatter);
config.Formatters.Add(jsonFormatter);
This way the JSON formatter will be the second registered formatter in the list, and the XML will be the first one.
This is content negotiator resposibility to choose the right formatter to serialize the response object. But by default WebApi framework gets JsonFormatter if could not find appropriate formatter.
If there are still no matches, the content negotiator simply picks the first formatter that can serialize the type.
So it is strange behavior. Anyway you could set up custom content negotiator to choose explicit JsonFormatter if request does not have Accept header.
public class JsonContentNegotiator : DefaultContentNegotiator
{
protected override MediaTypeFormatterMatch MatchAcceptHeader(IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptValues, MediaTypeFormatter formatter)
{
var defaultMatch = base.MatchAcceptHeader(sortedAcceptValues, formatter);
if (defaultMatch == null)
{
//Check to find json formatter
var jsonMediaType = formatter.SupportedMediaTypes.FirstOrDefault(h => h.MediaType == "application/json");
if (jsonMediaType != null)
{
return new MediaTypeFormatterMatch(formatter, jsonMediaType, 1.0, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral);
}
}
return defaultMatch;
}
}
And replace in HttpConfiguration object
config.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator());

ASP.NET localization

When localizing an ASP.NET app (MVC or webforms, does't matter), how do you handle HTML strings in your resource file? In particular, how do you handle something like a paragraph with an embedded dynamic link? My strategy so far has been to use some sort of placeholder for the href attribute value and replace it at runtime with the actual URL, but this seems hokey at best.
As an example, suppose my copy is:
Thank you for registering. Click
here
to update your preferences.
To login and begin using the app, click
here.
Using MVC (Razor), what could be a simple:
<p>#Resources.Strings.ThankYouMessage</p>
now turns into
<p>#Resources.Strings.ThankYouMessage
.Replace("{prefs_url}", Url.Action("Preferences", "User"))
.Replace("{login_url}", Url.Action("Login", "User"))</p>
It's not horrible, but I guess I'm just wondering if there's a better way?
There isn't really a better way, beyond some syntax and performance tweaks. For example, you might add a cache layer so that you aren't doing these string operations for every request. Something like this:
<p>#Resources.LocalizedStrings.ThankYouMessage</p>
which calls a function perhaps like this:
Localize("ThankYouMessage", Resources.Strings.ThankYouMessage)
which does a hashtable lookup by resource + culture:
//use Hashtable instead of Dictionary<> because DictionaryBase is not thread safe.
private static System.Collections.Hashtable _cache =
System.Collections.Hashtable.Synchronized(new Hashtable());
public static string Localize(string resourceName, string resourceContent) {
string cultureName = System.Threading.Thread.CurrentThread.CurrentCulture.Name;
if (string.IsNullOrEmpty(resourceName))
throw new ArgumentException("'resourceName' is null or empty.");
string cacheKey = resourceName + "/" + cultureName;
object o = _cache[cacheKey];
if (null == o) { //first generation; add it to the cache.
_cache[cacheKey] = o = ReplaceTokensWithValues(resourceContent);
}
return o as string;
}
Notice the call to ReplaceTokensWithValues(). That is the function that contains all the "not horrible" string-replacement fiffery:
public static string ReplaceTokensWithValues(string s) {
return s.Replace("{prefs_url}", Url.Action("Preferences", "User"))
.Replace("{login_url}", Url.Action("Login", "User")
.Replace("{any_other_stuff}", "random stuff");
}
By using a caching approach as above, ReplaceTokensWithValues() is only called once per culture, per resource for the lifetime of the application--instead of once per resource call. The difference may be on the order of 100 vs. 1,000,000.

Parse a JSON query response string in ASP.NET

Hey guys I have the following code working which is sending a GET Request and receiving JSON Response.
Now I can basically go
GetWeatherByLocation(53.3, -6.28);
and the method returns
{"status":"OK","url":"http://www.link.com/areas/rathfarnham-11","name":"Rathfarnham"}
I was now wondering how can I retrieve the values for
URL
Name
from the string returned
THanks a lot
Im using ASP.NET 2 this is my calling code
public static string GetWeatherByLocation(double lat, double lng)
{
string formattedUri = String.Format(CultureInfo.InvariantCulture,
FindNearbyWeatherUrl, lat, lng);
HttpWebRequest webRequest = GetWebRequest(formattedUri);
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
string jsonResponse = string.Empty;
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
jsonResponse = sr.ReadToEnd();
}
return jsonResponse;
}
Your calling code is in javascript, i guess. Just use the response as a javascript object, because the json format is a notation for a javascript object. You can access its properties directly:
var returningValue = {"status":"OK","url":"http://www.link.com/areas/rathfarnham-11","name":"Rathfarnham"} ;
alert(returningValue.status);
alert(returningValue.url);
alert(returningValue.name);
edit: if you want to parse json in .net you can see this question where somebody explains how to parse the object using JavaScriptSerializer from System.Web.Extensions.dll
edit 2: if your version of .net doesn't let you play with this .dll i'd recommend looking into json.net previous releases, notably the last 2.0 release. But you should have access to the dll since it appeared in .net 2.0, albeit in the AJAX framework if my memory is good...
It's possible to parse your object by hand, but i'd really recommend using a library instead. If you want to do it by hand, you're in a complex world...

NVelocity not finding the template

I'm having some difficulty with using NVelocity in an ASP.NET MVC application. I'm using it as a way of generating emails.
As far as I can make out the details I'm passing are all correct, but it fails to load the template.
Here is the code:
private const string defaultTemplatePath = "Views\\EmailTemplates\\";
...
velocityEngine = new VelocityEngine();
basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, defaultTemplatePath);
ExtendedProperties properties = new ExtendedProperties();
properties.Add(RuntimeConstants.RESOURCE_LOADER, "file");
properties.Add(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, basePath);
velocityEngine.Init(properties);
The basePath is the correct directory, I've pasted the value into explorer to ensure it is correct.
if (!velocityEngine.TemplateExists(name))
throw new InvalidOperationException(string.Format("Could not find a template named '{0}'", name));
Template result = velocityEngine.GetTemplate(name);
'name' above is a valid filename in the folder defined as basePath above. However, TemplateExists returns false. If I comment that conditional out and let it fail on the GetTemplate method call the stack trace looks like this:
at NVelocity.Runtime.Resource.ResourceManagerImpl.LoadResource(String resourceName, ResourceType resourceType, String encoding)
at NVelocity.Runtime.Resource.ResourceManagerImpl.GetResource(String resourceName, ResourceType resourceType, String encoding)
at NVelocity.Runtime.RuntimeInstance.GetTemplate(String name, String encoding)
at NVelocity.Runtime.RuntimeInstance.GetTemplate(String name)
at NVelocity.App.VelocityEngine.GetTemplate(String name)
...
I'm now at a bit of an impasse. I feel that the answer is blindingly obvious, but I just can't seem to see it at the moment.
Have you considered using Castle's NVelocityTemplateEngine?
Download from the "TemplateEngine Component 1.1 - September 29th, 2009" section and reference the following assemblies:
using Castle.Components.Common.TemplateEngine.NVelocityTemplateEngine;
using Castle.Components.Common.TemplateEngine;
Then you can simply call:
using (var writer = new StringWriter())
{
_templateEngine.Process(data, string.Empty, writer, _templateContents);
return writer.ToString();
}
Where:
_templateEngine is your NVelocityTemplateEngine
data is your Dictionary of information (I'm using a Dictionary to enable me to access objects by a key ($objectKeyName) in my template.
_templateContents is the actual template string itself.
I hope this is of help to you!
Just to add, you'll want to put that into a static method returning a string of course!
Had this issue recently - NVelocity needs to be initialised with the location of the template files. In this case mergeValues is an anonymous type so in my template I can just refer to $Values.SomeItem:
private string Merge(Object mergeValues)
{
var velocity = new VelocityEngine();
var props = new ExtendedProperties();
props.AddProperty("file.resource.loader.path", #"D:\Path\To\Templates");
velocity.Init(props);
var template = velocity.GetTemplate("MailTemplate.vm");
var context = new VelocityContext();
context.Put("Values", mergeValues);
using (var writer = new StringWriter())
{
template.Merge(context, writer);
return writer.ToString();
}
}
Try setting the file.resource.loader.path
http://weblogs.asp.net/george_v_reilly/archive/2007/03/06/img-srchttpwwwcodegenerationnetlogosnveloc.aspx
Okay - So I'm managed to get something working but it is a bit of a hack and isn't anywhere near a solution that I want, but it got something working.
Basically, I manually load in the template into a string then pass that string to the velocityEngine.Evaluate() method which writes the result into the the given StringWriter. The side effect of this is that the #parse instructions in the template don't work because it still cannot find the files.
using (StringWriter writer = new StringWriter())
{
velocityEngine.Evaluate(context, writer, templateName, template);
return writer.ToString();
}
In the code above templateName is irrelevant as it isn't used. template is the string that contains the entire template that has been pre-loaded from disk.
I'd still appreciate any better solutions as I really don't like this.
The tests are the ultimate authority:
http://fisheye2.atlassian.com/browse/castleproject/NVelocity/trunk/src/NVelocity.Tests/Test/ParserTest.cs?r=6005#l122
Or you could use the TemplateEngine component which is a thin wrapper around NVelocity that makes things easier.

How to programmatically clear outputcache for controller action method

If the controller action has the OutputCache attribute specified on an action, is there any way to clear the output cache without having to restart IIS?
[OutputCache (Duration=3600,VaryByParam="param1;param2")]
public string AjaxHtmlOutputMethod(string param1, string param2)
{
var someModel = SomeModel.Find( param1, param2 );
//set up ViewData
...
return RenderToString( "ViewName", someModel );
}
I'm looking at using HttpResponse.RemoveOutputCacheItem(string path) to clear it, but I'm having trouble figuring out what the path should be to map it to the action method. I'm going to try again with the aspx page that is rendered by ViewName.
Possibly I'll just manually insert the output of RenderToString into the HttpContext.Cache instead if I can't figure this one out.
Update
Please note that the OutputCache is VaryByParam, and testing out a hardcoded path "/controller/action" does not actually clear the outputcache, so it looks like it has to match "/controller/action/param1/param2".
That means I'll probably have to revert to object level caching and manually cache the output for RenderToString() :(
Try this
var urlToRemove = Url.Action("AjaxHtmlOutputMethod", "Controller");
HttpResponse.RemoveOutputCacheItem(urlToRemove);
UPDATED:
var requestContext = new System.Web.Routing.RequestContext(
new HttpContextWrapper(System.Web.HttpContext.Current),
new System.Web.Routing.RouteData());
var Url = new System.Web.Mvc.UrlHelper(requestContext);
UPDATED:
Try this:
[OutputCache(Location= System.Web.UI.OutputCacheLocation.Server, Duration=3600,VaryByParam="param1;param2")]
Otherwise the cache deletion won't work because you've
cached the HTML output on the user's machine
Further to the accepted answer, to support VaryByParam parameters:
[OutputCache (Duration=3600, VaryByParam="param1;param2", Location = OutputCacheLocation.Server)]
public string AjaxHtmlOutputMethod(string param1, string param2)
{
object routeValues = new { param1 = param1, param2 = param2 };
string url = Url.Action("AjaxHtmlOutputMethod", "Controller", routeValues);
Response.RemoveOutputCacheItem(url);
}
However Egor's answer is much better, because it supports all OutputCacheLocation values:
[OutputCache (Duration=3600, VaryByParam="param1;param2")]
public string AjaxHtmlOutputMethod(string param1, string param2)
{
if (error)
{
Response.Cache.SetNoStore();
Response.Cache.SetNoServerCaching();
}
}
When SetNoStore() and SetNoServerCaching() are called, they prevent the current Request being cached. Further requests will be cached, unless the functions are called for those requests as well.
This is ideal for handling error situations - when normally you want to cache responses, but not if they contain error messages.
I think correct flow is:
filterContext.HttpContext.Response.Cache.SetNoStore()
Add code to AjaxHtmlOutputMethod
HttpContext.Cache.Insert("Page", 1);
Response.AddCacheItemDependency("Page");
To clear output cache you can now use
HttpContext.Cache.Remove("Page");
Another option is to use VaryByCustom for the OutputCache and handle the invalidation of certain cache elements there.
Maybe it works for you, but it's not a general solution to your problem

Resources