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")
Related
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());
I want to return a View with a specific path to the view like so, return View(~/Views/Home). The type to be returned is an IViewComponentResult.
However, when the site gets rendered, it tries to find the view under: Components/{controller-name}/~/Views/Home.
So I want to ask you guys, if you know a smart way to delete Components/{controller-name}/ from the path. My view component class is deriving from the ViewComponent class.
The problem regarding this is that right now, you have to have a "Components" folder with the name of the controller as a subfolder which contains a Default.cshtml file. I do not like to have all my components inside one folder. I hope you have a solution.
The convention happened in ViewViewComponentResult hence if you don't want the convention, you will have to implement your own IViewComponentResult
since mvc is open source, you could copy all the ViewViewComponentResult and then change the convention bit.
so essentially two things has to be done:
create your own IViewComponentResult - lets call it MyViewViewComponentResult
create a helper in your implemented ViewComponent to replace the original View() - purpose is to returns the custom MyViewViewComponentResult you created at step 1
create your own IViewComponentResult
the convention happened at ExecuteAsync:
public class ViewViewComponentResult : IViewComponentResult
{
// {0} is the component name, {1} is the view name.
private const string ViewPathFormat = "Components/{0}/{1}";
private const string DefaultViewName = "Default";
....
public async Task ExecuteAsync(ViewComponentContext context)
{
....
if (result == null || !result.Success)
{
// This will produce a string like:
//
// Components/Cart/Default
//
// The view engine will combine this with other path info to search paths like:
//
// Views/Shared/Components/Cart/Default.cshtml
// Views/Home/Components/Cart/Default.cshtml
// Areas/Blog/Views/Shared/Components/Cart/Default.cshtml
//
// This supports a controller or area providing an override for component views.
var viewName = isNullOrEmptyViewName ? DefaultViewName : ViewName;
var qualifiedViewName = string.Format(
CultureInfo.InvariantCulture,
ViewPathFormat,
context.ViewComponentDescriptor.ShortName,
viewName);
result = viewEngine.FindView(viewContext, qualifiedViewName, isMainPage: false);
}
....
}
.....
}
so change it to your need
create a helper in your implemented ViewComponent which replace the original View
so base on original
/// <summary>
/// Returns a result which will render the partial view with name <paramref name="viewName"/>.
/// </summary>
/// <param name="viewName">The name of the partial view to render.</param>
/// <param name="model">The model object for the view.</param>
/// <returns>A <see cref="ViewViewComponentResult"/>.</returns>
public ViewViewComponentResult View<TModel>(string viewName, TModel model)
{
var viewData = new ViewDataDictionary<TModel>(ViewData, model);
return new ViewViewComponentResult
{
ViewEngine = ViewEngine,
ViewName = viewName,
ViewData = viewData
};
}
you would do something like
/// <summary>
/// Returns a result which will render the partial view with name <paramref name="viewName"/>.
/// </summary>
/// <param name="viewName">The name of the partial view to render.</param>
/// <param name="model">The model object for the view.</param>
/// <returns>A <see cref="ViewViewComponentResult"/>.</returns>
public MyViewViewComponentResult MyView<TModel>(string viewName, TModel model)
{
var viewData = new ViewDataDictionary<TModel>(ViewData, model);
return new MyViewViewComponentResult
{
ViewEngine = ViewEngine,
ViewName = viewName,
ViewData = viewData
};
}
so in future you would call MyView() instead of View()
more info check out another SO: Change component view location in Asp.Net 5
I run into a serious problem after changing from Atom to JSON (Light).
We use WCF Data Services 5.6 and configured it to use json in order to reduce the amount of data over the wire...
{"odata.error":{"code":"","message":{"lang":"de-AT","value":"An error occurred while processing this request."},
"innererror":{"message":"Multiple annotations with the name 'odata.bind' were detected for the property with name 'Employees'. In OData, duplicate annotations are not allowed.","type":"Microsoft.Data.OData.ODataException","stacktrace":"
at Microsoft.Data.OData.DuplicatePropertyNamesChecker.AddODataPropertyAnnotation(String propertyName, String annotationName, Object annotationValue)\r\n
at Microsoft.Data.OData.JsonLight.ODataJsonLightDeserializer.ProcessPropertyAnnotation(String annotatedPropertyName, String annotationName, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, Func`2 readPropertyAnnotationValue)\r\n
at Microsoft.Data.OData.JsonLight.ODataJsonLightDeserializer.ParseProperty(DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, Func`2 readPropertyAnnotationValue, String& parsedPropertyName)\r\n
at Microsoft.Data.OData.JsonLight.ODataJsonLightDeserializer.ProcessProperty(DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, Func`2 readPropertyAnnotationValue, Action`2 handleProperty)\r\n
at Microsoft.Data.OData.JsonLight.ODataJsonLightEntryAndFeedDeserializer.ReadEntryContent(IODataJsonLightReaderEntryState entryState)\r\n
at Microsoft.Data.OData.JsonLight.ODataJsonLightReader.ReadAtNavigationLinkEndImplementationSynchronously()\r\n
at Microsoft.Data.OData.JsonLight.ODataJsonLightReader.ReadAtNavigationLinkEndImplementation()\r\n
at Microsoft.Data.OData.ODataReaderCore.ReadImplementation()\r\n
at Microsoft.Data.OData.ODataReaderCore.ReadSynchronously()\r\n
at Microsoft.Data.OData.ODataReaderCore.InterceptException[T](Func`1 action)\r\n
at Microsoft.Data.OData.ODataReaderCore.Read()\r\n
at System.Data.Services.Serializers.EntityDeserializer.ReadEntry(ODataReader odataReader, SegmentInfo topLevelSegmentInfo)\r\n
at System.Data.Services.Serializers.EntityDeserializer.Read(SegmentInfo segmentInfo)\r\n
at System.Data.Services.Serializers.ODataMessageReaderDeserializer.Deserialize(SegmentInfo segmentInfo)"}}}
Our client side code looks like the following snippet:
ViewModel
//Insert control
var control = new Control
{
Name = this.Name,
ShortName = this.ShortName,
////etc...
};
this.ControlAgent.AddToContext(control);
foreach (var emp in this.EnforcingEmpColl.AddedItems)
{
this.ControlAgent.AddEnforcingEmployee(control, emp);
}
this.ControlAgent.SaveControl(control, (s1, e1) => { ////etc.. });
ControlAgent
public class ControlServiceAgent : ServiceAgentBase<Control>, IControlServiceAgent
{
/// <summary>
/// Initializes a new instance of the <see cref="ControlServiceAgent"/> class.
/// </summary>
public ControlServiceAgent()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ControlServiceAgent"/> class.
/// </summary>
/// <param name="uri">service uri</param>
public ControlServiceAgent(Uri uri)
: base(uri)
{
}
/// <summary>
/// Adds an Enforcing Emp to the Control
/// </summary>
/// <param name="control">control</param>
/// <param name="emp">enforcing Employee</param>
public void AddEnforcingEmployee(Control control, Employee emp)
{
this.Context.AttachTo("Employees", emp, "*");
this.Context.AddLink(control, "Employees", emp);
}
/// <summary>
/// Adds an control to the context
/// </summary>
/// <param name="entity">entity to add</param>
public void AddToContext(Control entity)
{
this.Context.AddToControls(entity);
}
/// <summary>
/// Adds and saves a control and notifies the tree
/// </summary>
/// <param name="entity">control to add</param>
/// <param name="handler">callback</param>
public void SaveControl(Control entity, EventHandler<ResponseEventArgs<Control>> handler)
{
this.Context.BeginSaveChanges(SaveChangesOptions.ReplaceOnUpdate,
ar =>
{
try
{
DataServiceResponse response = this.Context.EndSaveChanges(ar);
Control controlToAdd = null;
// Enumerate the returned responses.
foreach (ChangeOperationResponse change in response)
{
// Get the descriptor for the entity.
var descriptor = change.Descriptor as EntityDescriptor;
if (descriptor != null)
{
controlToAdd = descriptor.Entity as Control;
}
}
if (controlToAdd == null)
{
handler(this, new ResponseEventArgs<Control>("[SP-Control]: Error while SaveControl. Saving the added control failed!", null));
}
else
{
handler(this, new ResponseEventArgs<Control>(controlToAdd));
}
}
catch (Exception e)
{
handler(this, new ResponseEventArgs<Control>("[SP-Control]: Error while SaveControl. Saving the added control failed!", e));
}
}, null);
}
}
}
public abstract class ServiceAgentBase<T> : ServiceBase, IServiceAgentBase<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="ServiceAgentBase<T>"/> class.
/// </summary>
protected ServiceAgentBase() : base()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ServiceAgentBase<T>"/> class.
/// </summary>
/// <param name="uri">service uri</param>
protected ServiceAgentBase(Uri uri) : base(uri)
{
}
/// <summary>
/// Attaches an entity to the service context
/// </summary>
/// <param name="entity">entity to attach</param>
public virtual void Attach(T entity)
{
this.Context.Detach(entity);
////persist changes regardless of whether the underlying entity has changed
this.Context.AttachTo(entity.GetType().Name + "s", entity, "*");
}
/// <summary>
/// Deletes an entity
/// </summary>
/// <param name="entity">entity to delete</param>
public virtual void Delete(T entity)
{
this.Context.DeleteObject(entity);
}
/// <summary>
/// Updates an entity
/// </summary>
/// <param name="entity">entity to update</param>
public virtual void Update(T entity)
{
this.Context.UpdateObject(entity);
}
}
The only remarkable thing in ServiceBase is that we create the context and use Json
this.Context.Format.UseJson();
If I comment out this line and use Atompub instead, everything works fine...
Has anyone of you the same problem? Are there any solutions for that?
Thank you!
EDIT:
Traces from Fiddler
JSON:
POST http://localhost/MyService/MyDataService.svc/Controls HTTP/1.1
Accept: application/json;odata=minimalmetadata
DataServiceVersion: 3.0;NetFx
MaxDataServiceVersion: 3.0;NetFx
Content-Type: application/json;odata=minimalmetadata
{"odata.type":"Ria.DevelopmentModel.Control","Processes#odata.bind":
["http://localhost/MyService/MyDataService.svc/Processes(3)"],
"Employees#odata.bind":["http://localhost/MyService/MyDataService.svc/Employees(2627)"],
"Employees#odata.bind":["http://localhost/MyService/MyDataService.svc/Employees(2628)"],
"Accuracy":false,"Activity":null,"ActualStatus":0,"Automation":null,////All the other properties...}
ATOM
POST http://localhost/MyService/MyDataService.svc/Controls HTTP/1.1
Accept: application/atom+xml,application/xml
Content-Type: application/atom+xml
DataServiceVersion: 1.0;NetFx
MaxDataServiceVersion: 3.0;NetFx
<?xml version="1.0" encoding="utf-8"?><entry xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<category term="Ria.DevelopmentModel.Control" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Processes" type="application/atom+xml;type=feed" title="Processes" href="http://localhost/MyService/MyDataService.svc/Processes(3)" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Employees" type="application/atom+xml;type=feed" title="Employees" href="http://localhost/MyService/MyDataService.svc/Employees(2627)" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Employees" type="application/atom+xml;type=feed" title="Employees" href="http://localhost/MyService/MyDataService.svc/Employees(2628)" />
<id /><title /><updated>2013-09-18T09:20:07Z</updated><author><name /></author><content type="application/xml">
<m:properties><d:Accuracy m:type="Edm.Boolean">false</d:Accuracy><d:Activity m:null="true" /><d:ActualStatus m:type="Edm.Byte">0</d:ActualStatus><d:Automation m:type="Edm.Byte" m:null="true" /> ////All the other props....
This is indeed a bug in the WCF Data Services client library. Specifically, if you call AddLink() multiple times in the same request with the same value for sourceProperty ("Employees" in your example) and you're using Json, the client library will generate an incorrect payload.
It's pretty cumbersome, but as a workaround until a fix is available, you could try to make sure that every time BeginSaveChanges() is called, AddLink() has only been called once per property since the last call to BeginSaveChanges(). For example, if a user invokes AddEnforcingEmployee() several times before clicking the save control, you could internally keep track of that list of links instead of calling AddLink() on the context immediately. Then, when the user does click "save", you could call AddLink() followed by BeginSaveChanes() repeatedly for each AddEnforcingEmployee() call.
Alternatively, you could continue to use Atom instead of JSON.
Sorry I don't have a better answer for you at this point. I'll update this response when a fix is available. (And thank you for reporting this!)
I USE EF+MySql,the database contain two foriegnkey, and when i run my project,it say:There is already an open DataReader associated with this Connection which must be closed first, then i add MultipleActiveResultSets=true to connection string in web.config, i try again, it show me: The format of the initialization string does not meet specifications, how i can
fix this problem? the code difinite foriegnkey and the wrong initialization code list as below:
#region EDM 关系源元数据
[assembly: EdmRelationshipAttribute("blogModel", "cid", "cls", System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(blog.Models.cls), "news", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(blog.Models.news), true)]
[assembly: EdmRelationshipAttribute("blogModel", "uid", "users", System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(blog.Models.users), "news", System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(blog.Models.news), true)]
#endregion
#region 导航属性
/// <summary>
/// 没有元数据文档可用。
/// </summary>
[XmlIgnoreAttribute()]
[SoapIgnoreAttribute()]
[DataMemberAttribute()]
[EdmRelationshipNavigationPropertyAttribute("blogModel", "cid", "cls")]
public cls cls
{
get
{
return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<cls>("blogModel.cid", "cls").Value;
}
set
{
((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<cls>("blogModel.cid", "cls").Value = value;
}
}
/// <summary>
/// 没有元数据文档可用。
/// </summary>
[BrowsableAttribute(false)]
[DataMemberAttribute()]
public EntityReference<cls> clsReference
{
get
{
return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<cls>("blogModel.cid", "cls");
}
set
{
if ((value != null))
{
((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<cls>("blogModel.cid", "cls", value);
}
}
}
/// <summary>
/// 没有元数据文档可用。
/// </summary>
[XmlIgnoreAttribute()]
[SoapIgnoreAttribute()]
[DataMemberAttribute()]
[EdmRelationshipNavigationPropertyAttribute("blogModel", "uid", "users")]
public users users
{
get
{
return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<users>("blogModel.uid", "users").Value;
}
set
{
((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<users>("blogModel.uid", "users").Value = value;
}
}
/// <summary>
/// 没有元数据文档可用。
/// </summary>
[BrowsableAttribute(false)]
[DataMemberAttribute()]
public EntityReference<users> usersReference
{
get
{
return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<users>("blogModel.uid", "users");
}
set
{
if ((value != null))
{
((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedReference<users>("blogModel.uid", "users", value);
}
}
}
#endregion
public partial class blogEntities : ObjectContext
{
#region 构造函数
/// <summary>
/// 请使用应用程序配置文件的“blogEntities”部分中的连接字符串初始化新 blogEntities 对象。
/// </summary>
public blogEntities() : base("name=blogEntities", "blogEntities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
/// <summary>
/// 初始化新的 blogEntities 对象。
/// </summary>
public blogEntities(string connectionString) : base(connectionString, "blogEntities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
/// <summary>
/// 初始化新的 blogEntities 对象。
/// </summary>
public blogEntities(EntityConnection connection) : base(connection, "blogEntities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
#endregion
As I know MultipleActiveResultSets are not available on MySql so you cannot add it to connection string (it is for MS SQL).
The reason for this problem is most probably lazy loading. You are iterating result of some query and in the same time you access not loaded navigation properties inside the loop. That requires additional query to be executed and that query requires a new data reader (the first one is still not closed because you are just iterating its result set).
The solution:
Either materiealize whole result set of the query you want to iterate by using ToList or perhaps also AsEnumerable
Or eager load navigation properties you want to use inside the loop by using Include
Use different Open and close SQL connection names for each of the DataReaders. This will solve the issue.
As you are using same open and close database connection for multiple DataReader which is not supported by SQL server in VB.NET.