Controller that worked in ASP.NET Core 2.0:
[Produces("application/json")]
[Route("api/[controller]")]
[ApiController]
public class GraficResourcesApiController : ControllerBase
{
private readonly ApplicationDbContext _context;
public GraficResourcesApiController(ApplicationDbContext context)
{
_context = context;
}
[HttpGet]
public JsonResult GetGrafic(int ResourceId)
{
var sheduling = new List<Sheduling>();
var events = from e in _context.Grafic.Where(c=>c.ResourceId == ResourceId)
select new
{
id = e.Id,
title = e.Personals.Name,
start = e.DateStart,
end = e.DateStop,
color = e.Personals.Color,
personalId = e.PersonalId,
description = e.ClientName
};
var rows = events.ToArray();
return Json(rows);
}
}
in ASP.NET Core 2.1
return Json (rows);
writes that Json does not exist in the current context. If we remove Json leaving simply
return rows;
then writes that it was not possible to explicitly convert the type List () to JsonResult
How to convert to Json now?
In asp.net-core-2.1 ControllerBase does not have a Json(Object) method. However Controller does.
So either refactor the current controller to be derived from Controller
public class GraficResourcesApiController : Controller {
//...
}
to have access to the Controller.Json Method or you can initialize a new JsonResult yourself in the action
return new JsonResult(rows);
which is basically what the method does internally in Controller
/// <summary>
/// Creates a <see cref="JsonResult"/> object that serializes the specified <paramref name="data"/> object
/// to JSON.
/// </summary>
/// <param name="data">The object to serialize.</param>
/// <returns>The created <see cref="JsonResult"/> that serializes the specified <paramref name="data"/>
/// to JSON format for the response.</returns>
[NonAction]
public virtual JsonResult Json(object data)
{
return new JsonResult(data);
}
/// <summary>
/// Creates a <see cref="JsonResult"/> object that serializes the specified <paramref name="data"/> object
/// to JSON.
/// </summary>
/// <param name="data">The object to serialize.</param>
/// <param name="serializerSettings">The <see cref="JsonSerializerSettings"/> to be used by
/// the formatter.</param>
/// <returns>The created <see cref="JsonResult"/> that serializes the specified <paramref name="data"/>
/// as JSON format for the response.</returns>
/// <remarks>Callers should cache an instance of <see cref="JsonSerializerSettings"/> to avoid
/// recreating cached data with each call.</remarks>
[NonAction]
public virtual JsonResult Json(object data, JsonSerializerSettings serializerSettings)
{
if (serializerSettings == null)
{
throw new ArgumentNullException(nameof(serializerSettings));
}
return new JsonResult(data, serializerSettings);
}
Source
I am using Serilog HTTP sink for logging to Logstash in my .Net Core Project. In startup.cs I have following code to enable serilog.
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Http("http://mylogstashhost.com:5000").Enrich.WithProperty("user", "xxx").Enrich.WithProperty("serviceName", "yyy")
.MinimumLevel.Warning()
.CreateLogger();
And this code sends logs to the given http address. I can see on fiddler that following json is being posted to the logstash and logstash returns "ok" message.
{"events":[{"Timestamp":"2018-10-19T18:16:27.6561159+01:00","Level":"Warning","MessageTemplate":"abc","RenderedMessage":"abc","user":"xxx","serviceName":"yyy","Properties":{"ActionId":"b313b8ed-0baf-4d75-a6e2-f0dbcb941f67","ActionName":"MyProject.Controllers.HomeController.Index","RequestId":"0HLHLQMV1EBCJ:00000003","RequestPath":"/"}}]}
But when I checked on Kibana, I can not see this log. I tried to figure out what causes it and i realized that if I send the json as following format I can see the Log.
{"Timestamp":"2018-10-19T18:16:27.6561159+01:00","Level":"Warning","MessageTemplate":"abc","RenderedMessage":"abc","user":"xxx","serviceName":"yyy","Properties":{"ActionId":"b313b8ed-0baf-4d75-a6e2-f0dbcb941f67","ActionName":"MyProject.Controllers.HomeController.Index" ,"RequestId":"0HLHLQMV1EBCJ:00000003","RequestPath":"/"}}
So Logstash doesnt like the event to be in Events{} and also it wants "user" and "ServiceName" tags out of "Properties". Is there a way to format my Json like this?
Ok after some research and help, basically to achieve custom formats, one should implement interfaces like ITextFormatter, BatchFormatter etc.
I could achieve the format i need, by modifying ArrayBatchFormatter a little:
public class MyFormat : BatchFormatter
{
/// <summary>
/// Initializes a new instance of the <see cref="ArrayBatchFormatter"/> class.
/// </summary>
/// <param name="eventBodyLimitBytes">
/// The maximum size, in bytes, that the JSON representation of an event may take before it
/// is dropped rather than being sent to the server. Specify null for no limit. Default
/// value is 256 KB.
/// </param>
public MyFormat(long? eventBodyLimitBytes = 256 * 1024): base(eventBodyLimitBytes)
{
}
/// <summary>
/// Format the log events into a payload.
/// </summary>
/// <param name="logEvents">
/// The events to format.
/// </param>
/// <param name="output">
/// The payload to send over the network.
/// </param>
public override void Format(IEnumerable<string> logEvents, TextWriter output)
{
if (logEvents == null) throw new ArgumentNullException(nameof(logEvents));
if (output == null) throw new ArgumentNullException(nameof(output));
// Abort if sequence of log events is empty
if (!logEvents.Any())
{
return;
}
output.Write("[");
var delimStart = string.Empty;
foreach (var logEvent in logEvents)
{
if (string.IsNullOrWhiteSpace(logEvent))
{
continue;
}
int index = logEvent.IndexOf("{");
string adjustedString = "{\"user\":\"xxx\",\"serviceName\" : \"yyy\"," + logEvent.Substring(1);
if (CheckEventBodySize(adjustedString))
{
output.Write(delimStart);
output.Write(adjustedString);
delimStart = ",";
}
}
output.Write("]");
}
}
I would like to extend #nooaa answer with this variation. Instead of manipulating the string to add new objects, I would suggest using Newtonsoft.Json.Linq. This way you can append, add or remove existing properties of the object itself.
Also, instead of doing output.write after each event, you can combine all the output from the events and do output.write once at the end (a bit of performance)
public override void Format(IEnumerable<string> logEvents, TextWriter output)
{
if (logEvents == null) throw new ArgumentNullException(nameof(logEvents));
if (output == null) throw new ArgumentNullException(nameof(output));
// Abort if sequence of log events is empty
if (!logEvents.Any())
{
return;
}
List<object> updatedEvents = new List<object>();
foreach (string logEvent in logEvents)
{
if (string.IsNullOrWhiteSpace(logEvent))
{
continue;
}
// Parse the log event
var obj = JObject.Parse(logEvent);
// Add New entries
obj["#source_host"] = obj["fields"]["MachineName"].Value<string>().ToLower();
// Remove any entries you are not interested in
((JObject)obj["fields"]).Remove("MachineName");
// Default tags for any log that goes out of your app.
obj["#tags"] = new JArray() { "appName", "api" };
// Additional tags from end points (custom based on routes)
if (obj["fields"]["tags"] != null)
{
((JArray)obj["#tags"]).Merge((JArray)obj["fields"]["tags"]);
((JObject)obj["fields"]).Remove("tags");
}
updatedEvents.Add(obj);
}
output.Write(JsonConvert.SerializeObject(updatedEvents));
}
Update
Release Notes v8.0.0
With latest release, you dont override the method anymore.
namespace Serilog.Sinks.Http.BatchFormatters {
public class MyCustomFormatter: IBatchFormatter {
public void Format(IEnumerable<string> logEvents, TextWriter output) {
...
}
}
}
you don't provide any Contructors for it either.
Add queueLimitBytes along with batchFormatter and textFormatter
WriteTo.Http(new Uri(),
batchFormatter: new MyCustomFormatter(),
queueLimitBytes: 50 * ByteSize.MB,
textFormatter: new ElasticsearchJsonFormatter());
How to bind device image path windows phone.
Below is image path
"C://Data//Users//Public//Pictures//Camera Roll//WP_20141001_002.jpg"
Thanks
I'm not sure if in your case using string is a good choice - maybe it will be possible to use BitmapImage - obtain a StorageFile from path, open Stream and then set BitmapImage - in this case you perform async operations outside converter.
In case you still want to use string it's possible, but will need some special approach - using async methods along with binding. There is a very good article about aynchronous MVVM, written by Stephen Cleary. Basing on the article and other Stephen's answer I've made such a code:
First of all, we will have to define a Converter - it's little complicated as getting file and stream is asynchronous:
/// <summary>
/// Converter getting an image basing upon delivered path
/// </summary>
public class PathToImage : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var task = GetImage((String)value);
// the below class you will find in Stephen's answer mentioned above
return new TaskCompletionNotifier<BitmapImage>(task);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{ throw new NotImplementedException(); }
private async Task<BitmapImage> GetImage(string path)
{
StorageFile file = await StorageFile.GetFileFromPathAsync(path);
using (var stream = await file.OpenAsync(FileAccessMode.Read))
{
BitmapImage image = new BitmapImage();
image.SetSource(stream);
return image;
}
}
}
In our page we will need a property, which we will use in binding and set the DataContext:
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private string imagePath;
public string ImagePath
{
get { return imagePath; }
set { imagePath = value; RaiseProperty("ImagePath"); }
}
public MainPage()
{
this.InitializeComponent();
DataContext = this;
}
// rest of the code
Of course we have to define our binding - for example in XAML, it's little tricky as first we have to bind the DataContext to our Task then bind Source to the Result, which will be raised as the image is loaded:
<Image DataContext="{Binding ImagePath, Converter={StaticResource PathToImage}}" Stretch="Uniform"
Source="{Binding Result} HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
Once we have this all, we can set the property like this:
ImagePath = #"C:\Data\Users\Public\Pictures\Camera Roll\WP_20141001_002.jpg";
and we should see the result on the screen.
I want to keep all of my JavaScript code in one section; just before the closing body tag in my master layout page and just wondering the best to go about it, MVC style.
For example, if I create a DisplayTemplate\DateTime.cshtml file which uses jQuery UI's DateTime Picker than I would embed the JavaScript directly into that template but then it will render mid-page.
In my normal views I can just use #section JavaScript { //js here } and then #RenderSection("JavaScript", false) in my master layout but this doesn't seem to work in display/editor templates - any ideas?
You could proceed with a conjunction of two helpers:
public static class HtmlExtensions
{
public static MvcHtmlString Script(this HtmlHelper htmlHelper, Func<object, HelperResult> template)
{
htmlHelper.ViewContext.HttpContext.Items["_script_" + Guid.NewGuid()] = template;
return MvcHtmlString.Empty;
}
public static IHtmlString RenderScripts(this HtmlHelper htmlHelper)
{
foreach (object key in htmlHelper.ViewContext.HttpContext.Items.Keys)
{
if (key.ToString().StartsWith("_script_"))
{
var template = htmlHelper.ViewContext.HttpContext.Items[key] as Func<object, HelperResult>;
if (template != null)
{
htmlHelper.ViewContext.Writer.Write(template(null));
}
}
}
return MvcHtmlString.Empty;
}
}
and then in your _Layout.cshtml:
<body>
...
#Html.RenderScripts()
</body>
and somewhere in some template:
#Html.Script(
#<script src="#Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
)
Modified version of Darin's answer to ensure ordering. Also works with CSS:
public static IHtmlString Resource(this HtmlHelper HtmlHelper, Func<object, HelperResult> Template, string Type)
{
if (HtmlHelper.ViewContext.HttpContext.Items[Type] != null) ((List<Func<object, HelperResult>>)HtmlHelper.ViewContext.HttpContext.Items[Type]).Add(Template);
else HtmlHelper.ViewContext.HttpContext.Items[Type] = new List<Func<object, HelperResult>>() { Template };
return new HtmlString(String.Empty);
}
public static IHtmlString RenderResources(this HtmlHelper HtmlHelper, string Type)
{
if (HtmlHelper.ViewContext.HttpContext.Items[Type] != null)
{
List<Func<object, HelperResult>> Resources = (List<Func<object, HelperResult>>)HtmlHelper.ViewContext.HttpContext.Items[Type];
foreach (var Resource in Resources)
{
if (Resource != null) HtmlHelper.ViewContext.Writer.Write(Resource(null));
}
}
return new HtmlString(String.Empty);
}
You can add JS and CSS resources like this:
#Html.Resource(#<script src="#Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>, "js")
#Html.Resource(#<link rel="stylesheet" href="#Url.Content("~/CSS/style.css")" />, "css")
And render JS and CSS resources like this:
#Html.RenderResources("js")
#Html.RenderResources("css")
You could do a string check to see if it starts with script/link so you don't have to explicitly define what each resource is.
I faced the same problem, but solutions proposed here work good only for adding reference to the resource and are not very suitable for inline JS code. I found a very helpful article and wrapped all my inline JS (and also script tags) in
#using (Html.BeginScripts())
{
<script src="#Url.Content("~/Scripts/jquery-ui-1.8.18.min.js")" type="text/javascript"></script>
<script>
// my inline scripts here
<\script>
}
And in the _Layout view placed #Html.PageScripts() just before closing 'body' tag. Works like a charm for me.
The helpers themselves:
public static class HtmlHelpers
{
private class ScriptBlock : IDisposable
{
private const string scriptsKey = "scripts";
public static List<string> pageScripts
{
get
{
if (HttpContext.Current.Items[scriptsKey] == null)
HttpContext.Current.Items[scriptsKey] = new List<string>();
return (List<string>)HttpContext.Current.Items[scriptsKey];
}
}
WebViewPage webPageBase;
public ScriptBlock(WebViewPage webPageBase)
{
this.webPageBase = webPageBase;
this.webPageBase.OutputStack.Push(new StringWriter());
}
public void Dispose()
{
pageScripts.Add(((StringWriter)this.webPageBase.OutputStack.Pop()).ToString());
}
}
public static IDisposable BeginScripts(this HtmlHelper helper)
{
return new ScriptBlock((WebViewPage)helper.ViewDataContainer);
}
public static MvcHtmlString PageScripts(this HtmlHelper helper)
{
return MvcHtmlString.Create(string.Join(Environment.NewLine, ScriptBlock.pageScripts.Select(s => s.ToString())));
}
}
I liked the solution posted by #john-w-harding, so I combined it with the answer by #darin-dimitrov to make the following probably overcomplicated solution that lets you delay rendering any html (scripts too) within a using block.
USAGE
In a repeated partial view, only include the block one time:
#using (Html.Delayed(isOnlyOne: "MYPARTIAL_scripts")) {
<script>
someInlineScript();
</script>
}
In a (repeated?) partial view, include the block for every time the partial is used:
#using (Html.Delayed()) {
<b>show me multiple times, #Model.Whatever</b>
}
In a (repeated?) partial view, include the block once, and later render it specifically by name one-time:
#using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
<b>show me once by name</b>
<span>#Model.First().Value</span>
}
To render:
#Html.RenderDelayed(); // the "default" unidentified blocks
#Html.RenderDelayed("one-time", false); // render the specified block by name, and allow us to render it again in a second call
#Html.RenderDelayed("one-time"); // render the specified block by name
#Html.RenderDelayed("one-time"); // since it was "popped" in the last call, won't render anything
CODE
public static class HtmlRenderExtensions {
/// <summary>
/// Delegate script/resource/etc injection until the end of the page
/// <para>#via https://stackoverflow.com/a/14127332/1037948 and http://jadnb.wordpress.com/2011/02/16/rendering-scripts-from-partial-views-at-the-end-in-mvc/ </para>
/// </summary>
private class DelayedInjectionBlock : IDisposable {
/// <summary>
/// Unique internal storage key
/// </summary>
private const string CACHE_KEY = "DCCF8C78-2E36-4567-B0CF-FE052ACCE309"; // "DelayedInjectionBlocks";
/// <summary>
/// Internal storage identifier for remembering unique/isOnlyOne items
/// </summary>
private const string UNIQUE_IDENTIFIER_KEY = CACHE_KEY;
/// <summary>
/// What to use as internal storage identifier if no identifier provided (since we can't use null as key)
/// </summary>
private const string EMPTY_IDENTIFIER = "";
/// <summary>
/// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
/// </summary>
/// <param name="helper">the helper from which we use the context</param>
/// <param name="identifier">optional unique sub-identifier for a given injection block</param>
/// <returns>list of delayed-execution callbacks to render internal content</returns>
public static Queue<string> GetQueue(HtmlHelper helper, string identifier = null) {
return _GetOrSet(helper, new Queue<string>(), identifier ?? EMPTY_IDENTIFIER);
}
/// <summary>
/// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
/// </summary>
/// <param name="helper">the helper from which we use the context</param>
/// <param name="defaultValue">the default value to return if the cached item isn't found or isn't the expected type; can also be used to set with an arbitrary value</param>
/// <param name="identifier">optional unique sub-identifier for a given injection block</param>
/// <returns>list of delayed-execution callbacks to render internal content</returns>
private static T _GetOrSet<T>(HtmlHelper helper, T defaultValue, string identifier = EMPTY_IDENTIFIER) where T : class {
var storage = GetStorage(helper);
// return the stored item, or set it if it does not exist
return (T) (storage.ContainsKey(identifier) ? storage[identifier] : (storage[identifier] = defaultValue));
}
/// <summary>
/// Get the storage, but if it doesn't exist or isn't the expected type, then create a new "bucket"
/// </summary>
/// <param name="helper"></param>
/// <returns></returns>
public static Dictionary<string, object> GetStorage(HtmlHelper helper) {
var storage = helper.ViewContext.HttpContext.Items[CACHE_KEY] as Dictionary<string, object>;
if (storage == null) helper.ViewContext.HttpContext.Items[CACHE_KEY] = (storage = new Dictionary<string, object>());
return storage;
}
private readonly HtmlHelper helper;
private readonly string identifier;
private readonly string isOnlyOne;
/// <summary>
/// Create a new using block from the given helper (used for trapping appropriate context)
/// </summary>
/// <param name="helper">the helper from which we use the context</param>
/// <param name="identifier">optional unique identifier to specify one or many injection blocks</param>
/// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
public DelayedInjectionBlock(HtmlHelper helper, string identifier = null, string isOnlyOne = null) {
this.helper = helper;
// start a new writing context
((WebViewPage)this.helper.ViewDataContainer).OutputStack.Push(new StringWriter());
this.identifier = identifier ?? EMPTY_IDENTIFIER;
this.isOnlyOne = isOnlyOne;
}
/// <summary>
/// Append the internal content to the context's cached list of output delegates
/// </summary>
public void Dispose() {
// render the internal content of the injection block helper
// make sure to pop from the stack rather than just render from the Writer
// so it will remove it from regular rendering
var content = ((WebViewPage)this.helper.ViewDataContainer).OutputStack;
var renderedContent = content.Count == 0 ? string.Empty : content.Pop().ToString();
// if we only want one, remove the existing
var queue = GetQueue(this.helper, this.identifier);
// get the index of the existing item from the alternate storage
var existingIdentifiers = _GetOrSet(this.helper, new Dictionary<string, int>(), UNIQUE_IDENTIFIER_KEY);
// only save the result if this isn't meant to be unique, or
// if it's supposed to be unique and we haven't encountered this identifier before
if( null == this.isOnlyOne || !existingIdentifiers.ContainsKey(this.isOnlyOne) ) {
// remove the new writing context we created for this block
// and save the output to the queue for later
queue.Enqueue(renderedContent);
// only remember this if supposed to
if(null != this.isOnlyOne) existingIdentifiers[this.isOnlyOne] = queue.Count; // save the index, so we could remove it directly (if we want to use the last instance of the block rather than the first)
}
}
}
/// <summary>
/// <para>Start a delayed-execution block of output -- this will be rendered/printed on the next call to <see cref="RenderDelayed"/>.</para>
/// <para>
/// <example>
/// Print once in "default block" (usually rendered at end via <code>#Html.RenderDelayed()</code>). Code:
/// <code>
/// #using (Html.Delayed()) {
/// <b>show at later</b>
/// <span>#Model.Name</span>
/// etc
/// }
/// </code>
/// </example>
/// </para>
/// <para>
/// <example>
/// Print once (i.e. if within a looped partial), using identified block via <code>#Html.RenderDelayed("one-time")</code>. Code:
/// <code>
/// #using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
/// <b>show me once</b>
/// <span>#Model.First().Value</span>
/// }
/// </code>
/// </example>
/// </para>
/// </summary>
/// <param name="helper">the helper from which we use the context</param>
/// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
/// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
/// <returns>using block to wrap delayed output</returns>
public static IDisposable Delayed(this HtmlHelper helper, string injectionBlockId = null, string isOnlyOne = null) {
return new DelayedInjectionBlock(helper, injectionBlockId, isOnlyOne);
}
/// <summary>
/// Render all queued output blocks injected via <see cref="Delayed"/>.
/// <para>
/// <example>
/// Print all delayed blocks using default identifier (i.e. not provided)
/// <code>
/// #using (Html.Delayed()) {
/// <b>show me later</b>
/// <span>#Model.Name</span>
/// etc
/// }
/// </code>
/// -- then later --
/// <code>
/// #using (Html.Delayed()) {
/// <b>more for later</b>
/// etc
/// }
/// </code>
/// -- then later --
/// <code>
/// #Html.RenderDelayed() // will print both delayed blocks
/// </code>
/// </example>
/// </para>
/// <para>
/// <example>
/// Allow multiple repetitions of rendered blocks, using same <code>#Html.Delayed()...</code> as before. Code:
/// <code>
/// #Html.RenderDelayed(removeAfterRendering: false); /* will print */
/// #Html.RenderDelayed() /* will print again because not removed before */
/// </code>
/// </example>
/// </para>
/// </summary>
/// <param name="helper">the helper from which we use the context</param>
/// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
/// <param name="removeAfterRendering">only render this once</param>
/// <returns>rendered output content</returns>
public static MvcHtmlString RenderDelayed(this HtmlHelper helper, string injectionBlockId = null, bool removeAfterRendering = true) {
var stack = DelayedInjectionBlock.GetQueue(helper, injectionBlockId);
if( removeAfterRendering ) {
var sb = new StringBuilder(
#if DEBUG
string.Format("<!-- delayed-block: {0} -->", injectionBlockId)
#endif
);
// .count faster than .any
while (stack.Count > 0) {
sb.AppendLine(stack.Dequeue());
}
return MvcHtmlString.Create(sb.ToString());
}
return MvcHtmlString.Create(
#if DEBUG
string.Format("<!-- delayed-block: {0} -->", injectionBlockId) +
#endif
string.Join(Environment.NewLine, stack));
}
}
Install the Forloop.HtmlHelpers nuget package - it adds some helpers for managing scripts in partial views and editor templates.
Somewhere in your layout, you need to call
#Html.RenderScripts()
This will be where any script files and script blocks will be outputted in the page so I would recommend putting it after your main scripts in the layout and after a scripts section (if you have one).
If you're using The Web Optimization Framework with bundling, you can use the overload
#Html.RenderScripts(Scripts.Render)
so that this method is used for writing out script files.
Now, anytime you want to add script files or blocks in a view, partial view or template, simply use
#using (Html.BeginScriptContext())
{
Html.AddScriptFile("~/Scripts/jquery.validate.js");
Html.AddScriptBlock(
#<script type="text/javascript">
$(function() { $('#someField').datepicker(); });
</script>
);
}
The helpers ensure that only one script file reference is rendered if added multiple times and it also ensures that script files are rendered out in an expected order i.e.
Layout
Partials and Templates (in the order in which they appear in the view, top to bottom)
This post really helped me so I thought I would post my implementation of the basic idea. I've introduced a helper function that can return script tags for use in the #Html.Resource function.
I also added a simple static class so that I can use typed variables to identify a JS or CSS resource.
public static class ResourceType
{
public const string Css = "css";
public const string Js = "js";
}
public static class HtmlExtensions
{
public static IHtmlString Resource(this HtmlHelper htmlHelper, Func<object, dynamic> template, string Type)
{
if (htmlHelper.ViewContext.HttpContext.Items[Type] != null) ((List<Func<object, dynamic>>)htmlHelper.ViewContext.HttpContext.Items[Type]).Add(template);
else htmlHelper.ViewContext.HttpContext.Items[Type] = new List<Func<object, dynamic>>() { template };
return new HtmlString(String.Empty);
}
public static IHtmlString RenderResources(this HtmlHelper htmlHelper, string Type)
{
if (htmlHelper.ViewContext.HttpContext.Items[Type] != null)
{
List<Func<object, dynamic>> resources = (List<Func<object, dynamic>>)htmlHelper.ViewContext.HttpContext.Items[Type];
foreach (var resource in resources)
{
if (resource != null) htmlHelper.ViewContext.Writer.Write(resource(null));
}
}
return new HtmlString(String.Empty);
}
public static Func<object, dynamic> ScriptTag(this HtmlHelper htmlHelper, string url)
{
var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
var script = new TagBuilder("script");
script.Attributes["type"] = "text/javascript";
script.Attributes["src"] = urlHelper.Content("~/" + url);
return x => new HtmlString(script.ToString(TagRenderMode.Normal));
}
}
And in use
#Html.Resource(Html.ScriptTag("Areas/Admin/js/plugins/wysiwyg/jquery.wysiwyg.js"), ResourceType.Js)
Thanks to #Darin Dimitrov who supplied the answer in my question here.
The answer given in Populate a Razor Section From a Partial using the RequireScript HtmlHelper follows the same pattern. It also has the benefit that it checks for and suppresses duplicate references to the same Javascript URL, and it has an explicit priority parameter that can be used to control ordering.
I extended this solution by adding methods for:
// use this for scripts to be placed just before the </body> tag
public static string RequireFooterScript(this HtmlHelper html, string path, int priority = 1) { ... }
public static HtmlString EmitRequiredFooterScripts(this HtmlHelper html) { ... }
// use this for CSS links
public static string RequireCSS(this HtmlHelper html, string path, int priority = 1) { ... }
public static HtmlString EmitRequiredCSS(this HtmlHelper html) { ... }
I like Darin's & eth0's solutions though since they use the HelperResult template, which allows for script and CSS blocks, not just links to Javascript and CSS files.
#Darin Dimitrov and #eth0 answers to use with bundle extention usage :
#Html.Resources(a => new HelperResult(b => b.Write( System.Web.Optimization.Scripts.Render("~/Content/js/formBundle").ToString())), "jsTop")
If you have the following:
Asp.Net MVC 2 project having object classes that define view models.
Serialize these models to the web browser client using JSON.
Client adds information to the objects, like an Order Line on an Invoice.
Client sends back the object to the server for processing.
Is there any way to share with the client a data contract for the JSON objects? I would really want to have the server create a Order using an Order factory, then send it to the client. The client adds order lines using the data contracts, and sends back the fully populated object as JSON.
I would really like to do the following in JavaScript at the client:
var order = myService.OrderFactory.GetNewClientOrderRequest();
order.description = "Some New Order";
var orderLine = myService.OrderFactory.GetNewClientOrderLine( order);
orderLine.setProductID( 1234);
orderLine.setQty( 1);
order.AddLine( orderLine);
if( order.SubmitOrder() == true) {
//display confirmation
}
Any examples or web page links to Asp.Net MVC 2 would be very helpful.
Well, given an example model:
[DataContract]
public class Item
{
[DataMember]
public string Title { get; set; }
}
You could create an action filter which deserialises your objects from JSON:
/// <summary>
/// Deserialises a DataContract parameter from its JSON equivalence.
/// </summary>
public class JsonParameterFilter : ActionFilterAttribute
{
#region Properties
/// <summary>
/// Gets or sets the parameter name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the type of the parameter.
/// </summary>
public Type Type { get; set; }
#endregion
#region Methods
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.HttpContext.Request;
string contentType = request.ContentType;
if (!string.IsNullOrEmpty(contentType) && contentType.ToLower().Contains("application/json"))
{
var serialiser = new DataContractJsonSerializer(this.Type);
var #object = serialiser.ReadObject(request.InputStream);
filterContext.ActionParameters[Name] = #object;
}
}
#endregion
}
And applied to your action:
[JsonParameterFilter(Name = "item", Type = typeof(Item))]
public ActionResult ProcessItem(Item item)
{
// Do stuff here
return View();
}
What you need to make sure you do is post the data with the content type of "application/json":
var item = ItemFactory.GetNewItem();
item.Title = "Something";
var json = $.toJSON(item);
$.ajax({
contentType: "application/json; charset=utf-8"
data: json,
dataType: "json",
type: "post",
url: "Controller/Action",
});
Obviously your client-side library/script needs to be able to create an instance of a javascript object that can be deserialised as your server-side instance.