Uninitialised JsonSerializer in Breeze SaveBundleToSaveMap sample - asp.net-web-api

I'm attempting to use the SaveBundleToSaveMap snippet linked below to implement custom save handling on the server side of a breeze web api implementation.
SaveBundleToSaveMap
This sample does not work as is? (see below); their is a null reference exception which could use some attention.
The SaveWorkState(provider, entitiesArray) constructor calls the ContextProvider.CreateEntityInfoFromJson(...) method which then calls (the class scoped) JsonSerializer.Deserialize(new JTokenReader(jo), entityType) method.
The issue is that JsonSerializer is uninitialised and we get a null reference exeption.
For e.g. I added this test hack to get the code running:
protected internal EntityInfo CreateEntityInfoFromJson(dynamic jo, Type entityType) {
//temp fix to init JsonSerializer if SaveChanges has NOT been called
if(JsonSerializer==null) JsonSerializer = CreateJsonSerializer();
var entityInfo = CreateEntityInfo();
entityInfo.Entity = JsonSerializer.Deserialize(new JTokenReader(jo), entityType);
entityInfo.EntityState = (EntityState)Enum.Parse(typeof(EntityState), (String)jo.entityAspect.entityState);
entityInfo.ContextProvider = this;
This issue does not occur in the standard release bits as CreateEntityInfoFromJson is always? called downstream from a SaveChanges() call which means the JsonSerializer gets initialised.
However, things would be better structured if an initialised JsonSerializer was passed to CreateEntityInfoFromJson as a parameter to avoid potential future null reference issues?
Alternately, is there a way to get the SaveBundleToSaveMap snippet to init the JsonSerializer? Its got a private setter :(
UPDATE
Implemented a very hacky stopgap solution. If anyone at IdeaBlade is watching, would be great to have a public API to convert to and from json saveBundle <-> saveMap.
/// <summary>
/// Convert a json saveBundle into a breeze SaveMap
/// </summary>`enter code here`
public static Dictionary<Type, List<EntityInfo>> SaveBundleToSaveMap(JObject saveBundle)
{
var _dynSaveBundle = (dynamic)saveBundle;
var _entitiesArray = (JArray)_dynSaveBundle.entities;
var _provider = new BreezeAdapter();
//Hack 1: Breeze.ContextProvider initializes a global JsonSerializer in its SaveChanges() method
//We are bypassing SaveChanges() and bootstrapping directly into SaveWorkState logic to generate our saveMap
//as such we need to init a serializer here and slipsteam it in via reflection (its got a private setter)
var _serializerSettings = BreezeConfig.Instance.GetJsonSerializerSettings();
var _bootstrappedJsonSerializer = JsonSerializer.Create(_serializerSettings);
//Hack 2:
//How to write to a private setter via reflection
//http://stackoverflow.com/questions/3529270/how-can-a-private-member-accessable-in-derived-class-in-c
PropertyInfo _jsonSerializerProperty = _provider.GetType().GetProperty("JsonSerializer", BindingFlags.Instance | BindingFlags.NonPublic);
//Hack 3: JsonSerializer property is on Breeze.ContextProvider type; not our derived EFContextProvider type so...
_jsonSerializerProperty = _jsonSerializerProperty.DeclaringType.GetProperty("JsonSerializer", BindingFlags.Instance | BindingFlags.NonPublic);
//Finally, we can init the JsonSerializer
_jsonSerializerProperty.SetValue(_provider, _bootstrappedJsonSerializer);
//saveWorkState constructor loads json entitiesArray into saveWorkState.EntityInfoGroups struct
var _saveWorkState = new SaveWorkState(_provider, _entitiesArray);
//BeforeSave logic loads saveWorkState.EntityInfoGroups metadata into saveWorkState.SaveMap
_saveWorkState.BeforeSave();
var _saveMap = _saveWorkState.SaveMap;
return _saveMap;
}

I looked into this. You don't actually need to make a change to the Breeze code to accomplish what you want. The ContextProvider is designed such that you can do just about whatever you want during save.
I'm curious: what "custom save handling" do you want to perform that you can't do today with the BeforeSave and AfterSave logic? I see in your "stopgap" code that you're calling BeforeSave on the SaveWorkState. What more do you need?
As an exercise, I wrote a NorthwindIBDoNotSaveContext that does what you want. Here's how it goes:
/// <summary>
/// A context whose SaveChanges method does not save
/// but it will prepare its <see cref="SaveWorkState"/> (with SaveMap)
/// so developers can do what they please with the same information.
/// See the <see cref="GetSaveMapFromSaveBundle"/> method;
/// </summary>
public class NorthwindIBDoNotSaveContext : EFContextProvider<NorthwindIBContext_CF>
{
/// <summary>
/// Open whatever is the "connection" to the "database" where you store entity data.
/// This implementation does nothing.
/// </summary>
protected override void OpenDbConnection(){}
/// <summary>
/// Perform your custom save to wherever you store entity data.
/// This implementation does nothing.
/// </summary>
protected override void SaveChangesCore(SaveWorkState saveWorkState) {}
/// <summary>
/// Return the SaveMap that Breeze prepares
/// while performing <see cref="ContextProvider.SaveChanges"/>.
/// </summary>
/// <remarks>
/// Calls SaveChanges which internally creates a <see cref="SaveWorkState"/>
/// from the <see param="saveBundle"/> and then runs the BeforeSave and AfterSave logic (if any).
/// <para>
/// While this works, it is hacky if all you want is the SaveMap.
/// The real purpose of this context is to demonstrate how to
/// pare down a ContextProvider, benefit from the breeze save pre/post processing,
/// and then do your own save inside the <see cref="SaveChangesCore"/>.
/// </para>
/// </remarks>
/// <returns>
/// Returns the <see cref="SaveWorkState.SaveMap"/>.
/// </returns>
public Dictionary<Type, List<EntityInfo>> GetSaveMapFromSaveBundle(JObject saveBundle)
{
SaveChanges(saveBundle); // creates the SaveWorkState and SaveMap as a side-effect
return SaveWorkState.SaveMap;
}
}
And here's how you could use it to get the SaveMap:
var saveMap = new NorthwindIBDoNotSaveContext().GetSaveMapFromSaveBundle(saveBundle);
Yes, it is "hacky", particularly if all you want is the SaveMap. But why do you just want the SaveMap?
We've designed the ContextProvider (and all of its sub-classes) such that you have free reign over the SaveChangesCore method. You could override that, further manipulate the SaveMap, then either delegate to the base implementation or do whatever else you have in mind for saving the entity data.
But while I don't see what you're after, it was not all that hard to extract the SaveChanges initialization logic into its own method.
So in the next release (after 1.5.2), you should find the following new method in the ContextProvider:
protected void InitializeSaveState(JObject saveBundle)
{
JsonSerializer = CreateJsonSerializer();
var dynSaveBundle = (dynamic)saveBundle;
var entitiesArray = (JArray)dynSaveBundle.entities;
var dynSaveOptions = dynSaveBundle.saveOptions;
SaveOptions = (SaveOptions)JsonSerializer.Deserialize(new JTokenReader(dynSaveOptions), typeof(SaveOptions));
SaveWorkState = new SaveWorkState(this, entitiesArray);
}
SaveChanges now calls that method before continuing on in its previous manner:
public SaveResult SaveChanges(JObject saveBundle, TransactionSettings transactionSettings = null) {
if (SaveWorkState == null || SaveWorkState.WasUsed) {
InitializeSaveState(saveBundle);
}
transactionSettings = transactionSettings ?? BreezeConfig.Instance.GetTransactionSettings();
...
}
Notice that SaveChanges won't call InitializeSaveState twice if you've already prepared the SaveWorkState by, say, calling InitializeSaveState externally and then called SaveChanges immediately thereafter. It also won't save twice with a "used" SaveWorkState.
The source is checked into github right now if you're interested.
You'll be able to get the SaveMap from a save bundle by adding this method to your sub-class of a ContextProvider as in this example:
public class NorthwindContextProvider: EFContextProvider<NorthwindIBContext_CF> {
...
public Dictionary<Type, List<EntityInfo>> GetSaveMapFromSaveBundle(JObject saveBundle) {
InitializeSaveState(saveBundle); // Sets initial EntityInfos
SaveWorkState.BeforeSave(); // Creates the SaveMap as byproduct of BeforeSave logic
return SaveWorkState.SaveMap;
}
...
}
Now you use that as follows:
var saveMap = ContextProvider.GetSaveMapFromSaveBundle(saveBundle);

Related

Xamarin.UITest - Marked

I am writing automation tests using Xamarin.UITest. I have started adding Automation Ids to the Xaml files of the Xamarin project. I am wondering if there is increased efficiency in the tests by using Automation Id's over the Text within the UI Element?
What I have tried to figure out is if there is a order in which the Marked method performs a search of the UI tree. I decompiled the Xamarin.UITest to see what was specified in the Queries.Marked method.
It states in the method what it searches for on Android and iOS but it's not clear to me how this is achieved.
Has anyone got any knowledge on this?
/// <summary>
/// Matches common values.
/// For Android: An element with the given value as either <c>id</c>, <c>contentDescription</c> or <c>text</c>.
/// For iOS: An element with the given value as either <c>accessibilityLabel</c> or <c>accessibilityIdentifier</c>.
/// </summary>
/// <param name="text">The value to match.</param>
public AppQuery Marked(string text)
{
AppQuery appQuery = this;
if (!((IEnumerable<IQueryToken>) this._tokens).Any<IQueryToken>())
appQuery = new AppQuery(appQuery, new object[1]
{
(object) new HiddenToken("*")
});
return new AppQuery(appQuery, new object[1]
{
(object) new WrappingToken((IQueryToken) new StringPropertyToken<SingleQuoteEscapedString>("marked", new SingleQuoteEscapedString(text)), string.Format("Marked(\"{0}\")", (object) text))
});
}

How to display description of method parameter?

I have a method documented like this:
/// <summary>
/// Execute a call request to the endpoint specified
/// </summary>
/// <param name="URI">Endpoint of API</param>
public string Call()
{
return null;
}
But when I put the mouse over Call, I can't see the URI param description, only the Call method description. I also tried pressing ctrl + shift + space but nothing happens. An image example:
You have to add a parameter to the function an then mouse-over the parameter in order to see the description.
For example:
/// <summary>
/// Execute a call request to the endpoint specified
/// </summary>
/// <param name="uri">Endpoint of API</param>
public string Call(string uri)
{
return null;
}
demo image
Also, you can go to Tools -> Options -> Environment -> Keyboard or Default Keyboard Shortcuts in Visual Studio, you can then search for commands and see what is assigned to that (and remap).

With SerilogWeb.Owin discontinued, is there an "official" integration?

I came across the discontinuation notice of the SerilogWeb.Owin package, and in reading the GitHub issue there was discussion about "redirecting folks somewhere" given the ~5K+ downloads of the package.
But I haven't been able to figure out where I'm being redirected to!
So where should I be looking for a "Serilog-blessed" integration for using Serilog with an (OWIN) self-hosted Web API?
The package boils down to the following, which I derived from the issues in [the repo](https://github.com/serilog-web/owin/blob/master/src/SerilogWeb.Owin/Owin/LoggerFactory.cs
):
Adding a RequestId to allow traces to be correlated
using (Serilog.Context.LogContext.PushProperty("RequestId", Guid.NewGuid().ToString("N"))
Plug in a logger redirector:
app.SetLoggerFactory(new SerilogOwinFactory());
Copy in the impl (this works with WebApi 5.2.4, Katana 4.0, Serilog 2.6) and incorporates learnings from another question about efficiently forwarding events without the writing treating the message as a template, and ensuring the Exception is included a first class element of the message in order that it can be formatted and/or demystyfied corrrectly:
And the good news is that, when you reach ASP.NET Core, there will be a fully maintained package with a deeper integration waiting for you, with a one-liner hookin ;)
// From https://github.com/serilog-web/owin/blob/master/src/SerilogWeb.Owin/Owin/LoggerFactory.cs
// Copyright 2015 SerilogWeb, Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.Owin.Logging;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using ILogger = Serilog.ILogger;
namespace SerilogWeb.Owin
{
/// <summary>
/// Implementation of Microsoft.Owin.Logger.ILoggerFactory.
/// </summary>
public class SerilogOwinFactory : ILoggerFactory
{
readonly Func<ILogger> _getLogger;
readonly Func<TraceEventType, LogEventLevel> _getLogEventLevel;
/// <summary>
/// Create a logger factory.
/// </summary>
/// <param name="logger">The logger; if not provided the global <see cref="Serilog.Log.Logger"/> will be used.</param>
/// <param name="getLogEventLevel"></param>
public SerilogOwinFactory(ILogger logger = null, Func<TraceEventType, LogEventLevel> getLogEventLevel = null)
{
_getLogger = logger == null ? (Func<ILogger>)(() => Log.Logger) : (() => logger);
_getLogEventLevel = getLogEventLevel ?? ToLogEventLevel;
}
/// <summary>
/// Creates a new ILogger instance of the given name.
/// </summary>
/// <param name="name">The logger context name.</param>
/// <returns>A logger instance.</returns>
public Microsoft.Owin.Logging.ILogger Create(string name)
{
return new Logger(_getLogger().ForContext(Constants.SourceContextPropertyName, name), _getLogEventLevel);
}
static LogEventLevel ToLogEventLevel(TraceEventType traceEventType)
{
switch (traceEventType)
{
case TraceEventType.Critical:
return LogEventLevel.Fatal;
case TraceEventType.Error:
return LogEventLevel.Error;
case TraceEventType.Warning:
return LogEventLevel.Warning;
case TraceEventType.Information:
return LogEventLevel.Information;
case TraceEventType.Verbose:
return LogEventLevel.Verbose;
case TraceEventType.Start:
return LogEventLevel.Debug;
case TraceEventType.Stop:
return LogEventLevel.Debug;
case TraceEventType.Suspend:
return LogEventLevel.Debug;
case TraceEventType.Resume:
return LogEventLevel.Debug;
case TraceEventType.Transfer:
return LogEventLevel.Debug;
default:
throw new ArgumentOutOfRangeException("traceEventType");
}
}
class Logger : Microsoft.Owin.Logging.ILogger
{
readonly ILogger _logger;
readonly Func<TraceEventType, LogEventLevel> _getLogEventLevel;
static readonly Exception _exceptionPlaceHolder = new Exception("(Exception enclosed)");
internal Logger(ILogger logger, Func<TraceEventType, LogEventLevel> getLogEventLevel)
{
_logger = logger;
_getLogEventLevel = getLogEventLevel;
}
public bool WriteCore(TraceEventType eventType, int eventId, object state, Exception exception, Func<object, Exception, string> formatter)
{
var level = _getLogEventLevel(eventType);
// According to docs http://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin/Logging/ILogger.cs
// "To check IsEnabled call WriteCore with only TraceEventType and check the return value, no event will be written."
if (state == null)
return _logger.IsEnabled(level);
if (!_logger.IsEnabled(level))
return false;
var formattedMessage = formatter(state, null); // Omit exception as we're including it in the LogEvent
var template = new Serilog.Events.MessageTemplate(new[] { new Serilog.Parsing.TextToken(formattedMessage) });
var logEvent = new Serilog.Events.LogEvent(DateTimeOffset.Now, level, exception, template, Enumerable.Empty<Serilog.Events.LogEventProperty>());
_logger.ForContext("eventId", eventId).Write(logEvent);
return true;
}
}
}
}

MvvmCross - navigation with custom objects

I have followed the steps in this link
Passing complex navigation parameters with MvvmCross ShowViewModel
i implemented an instance of the IMvxJsonConverter, and registered it. this is my code for my view model
public class AccountDetailsViewModel : BaseViewModel<AccountDetailsNav>
{
private readonly Repository.AccountsRepository _accounts;
Account _fullAccount;
public AccountDetailsViewModel(Repository.AccountsRepository accounts)
{
_accounts = accounts;
}
protected override void RealInit(AccountDetailsNav parameter)
{
//stuff
}
I have tried simple types by just passing thru strings , this is the code i use to navigate to to the viewmodel
Mvx.RegisterSingleton<Repository.AccountsRepository>(() =>
{
return _accounts;
});
ShowViewModel<AccountDetailsViewModel>(nav);
But it never ever seems to arrive in my view model methods or populates my data, and i cannot for the life of me figure out why. the data is serialized fine , and i have even tried blank constructors to no avail .. i just cannot figure out why its not hitting the realinit
K i found the problem , when adding a new view i failed to remove this method on the code behind of the view, and as such was causing my viewmodel to be null and never hitting my breakpoints
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached.
/// This parameter is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}

How does "see" work in the method docs?

/// <summary>
/// Get all following siblings of each element up to but not including the element matched by the selector.
/// </summary>
/// <param name="selector">A string containing a selector expression to indicate where to stop matching following sibling elements.</param>
/// <see cref="http://api.jquery.com/nextUntil/"/>
/// <returns></returns>
public SharpQuery NextUntil(string selector = null)
{
throw new NotImplementedException();
}
I wanted to add a link in my method docs to link to a fuller explanation. "see" seemed appropriate for this (intellisense suggested it). However, when I call start typing my method, "see" doesn't appear in the tooltip. Is there a way to go to that link? I tried pressing F1, it took me to MSDN instead.
The <see> tag must be used within the text of other comment tags in order to specify a hyperlink.
You can also use <seealso> to specify a hyperlink to appear in a See Also section of the generated documentation.
MSDN provides the following example:
/// text for class TestClass
public class TestClass
{
/// <summary>DoWork is a method in the TestClass class.
/// <para>Here's how you could make a second paragraph in a description. <see cref="System.Console.WriteLine(System.String)"/> for information about output statements.</para>
/// <seealso cref="TestClass.Main"/>
/// </summary>
public static void DoWork(int Int1)
{
}
/// text for Main
static void Main()
{
}
}
From what I gathered at:
http://msdn.microsoft.com/en-us/library/5ast78ax(VS.80).aspx
This tags ( , ) will be available in the generated documentation file (the XML file, when you do /doc compiler options), and then further processed by tool like Sandcastle

Resources