PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/Breeze.WebApi/ContextProvider.cs

https://github.com/WilliamBZA/breeze.server.net
C# | 509 lines | 390 code | 91 blank | 28 comment | 49 complexity | d9b9a43e204f50e4aeed6f5f6d230d75 MD5 | raw file
  1. using Newtonsoft.Json;
  2. using Newtonsoft.Json.Converters;
  3. using Newtonsoft.Json.Linq;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Data;
  7. using System.Globalization;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Net;
  11. using System.Net.Http;
  12. using System.Reflection;
  13. using System.Transactions;
  14. using System.Web.Http;
  15. using System.Xml.Linq;
  16. namespace Breeze.WebApi {
  17. // Base for EFContextProvider
  18. public abstract class ContextProvider {
  19. public IKeyGenerator KeyGenerator { get; set; }
  20. public static SaveOptions ExtractSaveOptions(dynamic dynSaveBundle) {
  21. var jsonSerializer = CreateJsonSerializer();
  22. var dynSaveOptions = dynSaveBundle.saveOptions;
  23. var saveOptions = (SaveOptions)jsonSerializer.Deserialize(new JTokenReader(dynSaveOptions), typeof(SaveOptions));
  24. return saveOptions;
  25. }
  26. public SaveOptions SaveOptions { get; set; }
  27. public string Metadata() {
  28. lock (_metadataLock) {
  29. if (_jsonMetadata == null) {
  30. _jsonMetadata = BuildJsonMetadata();
  31. }
  32. return _jsonMetadata;
  33. }
  34. }
  35. public static String XDocToJson(XDocument xDoc) {
  36. var sw = new StringWriter();
  37. using (var jsonWriter = new JsonPropertyFixupWriter(sw)) {
  38. // jsonWriter.Formatting = Newtonsoft.Json.Formatting.Indented;
  39. var jsonSerializer = new JsonSerializer();
  40. var converter = new XmlNodeConverter();
  41. jsonSerializer.Converters.Add(converter);
  42. jsonSerializer.Serialize(jsonWriter, xDoc);
  43. }
  44. var jsonText = sw.ToString();
  45. return jsonText;
  46. }
  47. public SaveResult SaveChanges(JObject saveBundle, TransactionSettings transactionSettings = null) {
  48. JsonSerializer = CreateJsonSerializer();
  49. var dynSaveBundle = (dynamic)saveBundle;
  50. var entitiesArray = (JArray)dynSaveBundle.entities;
  51. var dynSaveOptions = dynSaveBundle.saveOptions;
  52. SaveOptions = (SaveOptions) JsonSerializer.Deserialize(new JTokenReader(dynSaveOptions), typeof(SaveOptions));
  53. SaveWorkState = new SaveWorkState(this, entitiesArray);
  54. transactionSettings = transactionSettings ?? BreezeConfig.Instance.GetTransactionSettings();
  55. try {
  56. if (transactionSettings.TransactionType == TransactionType.TransactionScope) {
  57. var txOptions = transactionSettings.ToTransactionOptions();
  58. using (var txScope = new TransactionScope(TransactionScopeOption.Required, txOptions)) {
  59. OpenAndSave(SaveWorkState);
  60. txScope.Complete();
  61. }
  62. } else if (transactionSettings.TransactionType == TransactionType.DbTransaction) {
  63. this.OpenDbConnection();
  64. using (IDbTransaction tran = BeginTransaction(transactionSettings.IsolationLevelAs)) {
  65. try {
  66. OpenAndSave(SaveWorkState);
  67. tran.Commit();
  68. } catch {
  69. tran.Rollback();
  70. throw;
  71. }
  72. }
  73. } else {
  74. OpenAndSave(SaveWorkState);
  75. }
  76. } catch (EntityErrorsException e) {
  77. // SaveWorkState.EntityErrors = e.EntityErrors;
  78. var error = new SaveError(e.EntityErrors);
  79. var resp = new HttpResponseMessage(e.StatusCode) {
  80. Content = new ObjectContent(typeof(SaveError), error, JsonFormatter.Create()),
  81. ReasonPhrase = e.Message ?? "Entity Errors exception"
  82. };
  83. throw new HttpResponseException(resp);
  84. } catch(Exception e2) {
  85. if (!HandleSaveException(e2, SaveWorkState)) {
  86. throw;
  87. }
  88. } finally {
  89. CloseDbConnection();
  90. }
  91. return SaveWorkState.ToSaveResult();
  92. }
  93. // allows subclasses to plug in own save exception handling
  94. // either throw an exception here, return false or return true and modify the saveWorkState.
  95. protected virtual bool HandleSaveException(Exception e, SaveWorkState saveWorkState) {
  96. return false;
  97. }
  98. private void OpenAndSave(SaveWorkState saveWorkState) {
  99. OpenDbConnection(); // ensure connection is available for BeforeSaveEntities
  100. saveWorkState.BeforeSave();
  101. SaveChangesCore(saveWorkState);
  102. saveWorkState.AfterSave();
  103. }
  104. private static JsonSerializer CreateJsonSerializer() {
  105. var serializerSettings = BreezeConfig.Instance.GetJsonSerializerSettings();
  106. var jsonSerializer = JsonSerializer.Create(serializerSettings);
  107. return jsonSerializer;
  108. }
  109. #region abstract and virtual methods
  110. /// <summary>
  111. /// Should only be called from BeforeSaveEntities and AfterSaveEntities.
  112. /// </summary>
  113. /// <returns>Open DbConnection used by the ContextProvider's implementation</returns>
  114. public abstract IDbConnection GetDbConnection();
  115. /// <summary>
  116. /// Internal use only. Should only be called by ContextProvider during SaveChanges.
  117. /// Opens the DbConnection used by the ContextProvider's implementation.
  118. /// Method must be idempotent; after it is called the first time, subsequent calls have no effect.
  119. /// </summary>
  120. protected abstract void OpenDbConnection();
  121. /// <summary>
  122. /// Internal use only. Should only be called by ContextProvider during SaveChanges.
  123. /// Closes the DbConnection used by the ContextProvider's implementation.
  124. /// </summary>
  125. protected abstract void CloseDbConnection();
  126. protected virtual IDbTransaction BeginTransaction(System.Data.IsolationLevel isolationLevel) {
  127. var conn = GetDbConnection();
  128. if (conn == null) return null;
  129. return conn.BeginTransaction(isolationLevel);
  130. }
  131. protected abstract String BuildJsonMetadata();
  132. protected abstract void SaveChangesCore(SaveWorkState saveWorkState);
  133. public virtual object[] GetKeyValues(EntityInfo entityInfo) {
  134. throw new NotImplementedException();
  135. }
  136. protected virtual EntityInfo CreateEntityInfo() {
  137. return new EntityInfo();
  138. }
  139. public EntityInfo CreateEntityInfo(Object entity, EntityState entityState = EntityState.Added) {
  140. var ei = CreateEntityInfo();
  141. ei.Entity = entity;
  142. ei.EntityState = entityState;
  143. ei.ContextProvider = this;
  144. return ei;
  145. }
  146. public Func<EntityInfo, bool> BeforeSaveEntityDelegate { get; set; }
  147. public Func<Dictionary<Type, List<EntityInfo>>, Dictionary<Type, List<EntityInfo>>> BeforeSaveEntitiesDelegate { get; set; }
  148. public Action<Dictionary<Type, List<EntityInfo>>, List<KeyMapping>> AfterSaveEntitiesDelegate { get; set; }
  149. /// <summary>
  150. /// The method is called for each entity to be saved before the save occurs. If this method returns 'false'
  151. /// then the entity will be excluded from the save. There is no need to call the base implementation of this
  152. /// method when overriding it.
  153. /// </summary>
  154. /// <param name="entityInfo"></param>
  155. /// <returns></returns>
  156. protected internal virtual bool BeforeSaveEntity(EntityInfo entityInfo) {
  157. if (BeforeSaveEntityDelegate != null) {
  158. return BeforeSaveEntityDelegate(entityInfo);
  159. } else {
  160. return true;
  161. }
  162. }
  163. protected internal virtual Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap) {
  164. if (BeforeSaveEntitiesDelegate != null) {
  165. return BeforeSaveEntitiesDelegate(saveMap);
  166. } else {
  167. return saveMap;
  168. }
  169. }
  170. protected internal virtual void AfterSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap, List<KeyMapping> keyMappings) {
  171. if (AfterSaveEntitiesDelegate != null) {
  172. AfterSaveEntitiesDelegate(saveMap, keyMappings);
  173. }
  174. }
  175. #endregion
  176. protected internal EntityInfo CreateEntityInfoFromJson(dynamic jo, Type entityType) {
  177. var entityInfo = CreateEntityInfo();
  178. entityInfo.Entity = JsonSerializer.Deserialize(new JTokenReader(jo), entityType);
  179. entityInfo.EntityState = (EntityState)Enum.Parse(typeof(EntityState), (String)jo.entityAspect.entityState);
  180. entityInfo.ContextProvider = this;
  181. entityInfo.UnmappedValuesMap = JsonToDictionary(jo.__unmapped);
  182. entityInfo.OriginalValuesMap = JsonToDictionary(jo.entityAspect.originalValuesMap);
  183. var autoGeneratedKey = jo.entityAspect.autoGeneratedKey;
  184. if (entityInfo.EntityState == EntityState.Added && autoGeneratedKey != null) {
  185. entityInfo.AutoGeneratedKey = new AutoGeneratedKey(entityInfo.Entity, autoGeneratedKey);
  186. }
  187. return entityInfo;
  188. }
  189. private Dictionary<String, Object> JsonToDictionary(dynamic json) {
  190. if (json == null) return null;
  191. var jprops = ((System.Collections.IEnumerable)json).Cast<JProperty>();
  192. var dict = jprops.ToDictionary(jprop => jprop.Name, jprop => {
  193. var val = jprop.Value as JValue;
  194. if (val != null) {
  195. return val.Value;
  196. } else {
  197. return jprop.Value as JObject;
  198. }
  199. });
  200. return dict;
  201. }
  202. protected internal Type LookupEntityType(String entityTypeName) {
  203. var delims = new string[] { ":#" };
  204. var parts = entityTypeName.Split(delims, StringSplitOptions.None);
  205. var shortName = parts[0];
  206. var ns = parts[1];
  207. var typeName = ns + "." + shortName;
  208. var type = BreezeConfig.ProbeAssemblies.Value
  209. .Select(a => a.GetType(typeName, false, true))
  210. .FirstOrDefault(t => t != null);
  211. if (type != null) {
  212. return type;
  213. } else {
  214. throw new ArgumentException("Assembly could not be found for " + entityTypeName);
  215. }
  216. }
  217. protected static Lazy<Type> KeyGeneratorType = new Lazy<Type>(() => {
  218. var typeCandidates = BreezeConfig.ProbeAssemblies.Value.Concat(new Assembly[] { typeof(IKeyGenerator).Assembly })
  219. .SelectMany(a => a.GetTypes()).ToList();
  220. var generatorTypes = typeCandidates.Where(t => typeof(IKeyGenerator).IsAssignableFrom(t) && !t.IsAbstract)
  221. .ToList();
  222. if (generatorTypes.Count == 0) {
  223. throw new Exception("Unable to locate a KeyGenerator implementation.");
  224. }
  225. return generatorTypes.First();
  226. });
  227. protected SaveWorkState SaveWorkState { get; private set; }
  228. protected JsonSerializer JsonSerializer { get; private set; }
  229. private object _metadataLock = new object();
  230. private string _jsonMetadata;
  231. }
  232. public class SaveWorkState {
  233. public SaveWorkState(ContextProvider contextProvider, JArray entitiesArray) {
  234. ContextProvider = contextProvider;
  235. var jObjects = entitiesArray.Select(jt => (dynamic)jt).ToList();
  236. var groups = jObjects.GroupBy(jo => (String)jo.entityAspect.entityTypeName).ToList();
  237. EntityInfoGroups = groups.Select(g => {
  238. var entityType = ContextProvider.LookupEntityType(g.Key);
  239. var entityInfos = g.Select(jo => ContextProvider.CreateEntityInfoFromJson(jo, entityType)).Cast<EntityInfo>().ToList();
  240. return new EntityGroup() { EntityType = entityType, EntityInfos = entityInfos };
  241. }).ToList();
  242. }
  243. public void BeforeSave() {
  244. SaveMap = new Dictionary<Type, List<EntityInfo>>();
  245. EntitiesWithAutoGeneratedKeys = new List<EntityInfo>();
  246. EntityInfoGroups.ForEach(eg => {
  247. var entityInfos = eg.EntityInfos.Where(ei => ContextProvider.BeforeSaveEntity(ei)).ToList();
  248. EntitiesWithAutoGeneratedKeys.AddRange(entityInfos.Where(ei => ei.AutoGeneratedKey != null));
  249. SaveMap.Add(eg.EntityType, entityInfos);
  250. });
  251. SaveMap = ContextProvider.BeforeSaveEntities(SaveMap);
  252. }
  253. public void AfterSave() {
  254. ContextProvider.AfterSaveEntities(SaveMap, KeyMappings);
  255. }
  256. public ContextProvider ContextProvider;
  257. protected List<EntityGroup> EntityInfoGroups;
  258. public Dictionary<Type, List<EntityInfo>> SaveMap { get; set; }
  259. public List<EntityInfo> EntitiesWithAutoGeneratedKeys { get; set; }
  260. public List<KeyMapping> KeyMappings;
  261. public List<EntityError> EntityErrors;
  262. public class EntityGroup {
  263. public Type EntityType;
  264. public List<EntityInfo> EntityInfos;
  265. }
  266. public SaveResult ToSaveResult() {
  267. if (EntityErrors != null) {
  268. return new SaveResult() { Errors = EntityErrors.Cast<Object>().ToList() };
  269. } else {
  270. var entities = SaveMap.SelectMany(kvp => kvp.Value.Select(entityInfo => entityInfo.Entity)).ToList();
  271. return new SaveResult() { Entities = entities, KeyMappings = KeyMappings };
  272. }
  273. }
  274. }
  275. public class SaveOptions {
  276. public bool AllowConcurrentSaves { get; set; }
  277. public Object Tag { get; set; }
  278. }
  279. public interface IKeyGenerator {
  280. void UpdateKeys(List<TempKeyInfo> keys);
  281. }
  282. // instances of this sent to KeyGenerator
  283. public class TempKeyInfo {
  284. public TempKeyInfo(EntityInfo entityInfo) {
  285. _entityInfo = entityInfo;
  286. }
  287. public Object Entity {
  288. get { return _entityInfo.Entity; }
  289. }
  290. public Object TempValue {
  291. get { return _entityInfo.AutoGeneratedKey.TempValue; }
  292. }
  293. public Object RealValue {
  294. get { return _entityInfo.AutoGeneratedKey.RealValue; }
  295. set { _entityInfo.AutoGeneratedKey.RealValue = value; }
  296. }
  297. public PropertyInfo Property {
  298. get { return _entityInfo.AutoGeneratedKey.Property; }
  299. }
  300. private EntityInfo _entityInfo;
  301. }
  302. [Flags]
  303. public enum EntityState {
  304. Detached = 1,
  305. Unchanged = 2,
  306. Added = 4,
  307. Deleted = 8,
  308. Modified = 16,
  309. }
  310. public class EntityInfo {
  311. protected internal EntityInfo() {
  312. }
  313. public ContextProvider ContextProvider { get; internal set; }
  314. public Object Entity { get; internal set; }
  315. public EntityState EntityState { get; set; }
  316. public Dictionary<String, Object> OriginalValuesMap { get; set; }
  317. public bool ForceUpdate { get; set; }
  318. public AutoGeneratedKey AutoGeneratedKey;
  319. public Dictionary<String, Object> UnmappedValuesMap { get; internal set; }
  320. }
  321. public enum AutoGeneratedKeyType {
  322. None,
  323. Identity,
  324. KeyGenerator
  325. }
  326. public class AutoGeneratedKey {
  327. public AutoGeneratedKey(Object entity, dynamic autoGeneratedKey) {
  328. Entity = entity;
  329. PropertyName = autoGeneratedKey.propertyName;
  330. AutoGeneratedKeyType = (AutoGeneratedKeyType)Enum.Parse(typeof(AutoGeneratedKeyType), (String)autoGeneratedKey.autoGeneratedKeyType);
  331. // TempValue and RealValue will be set later. - TempValue during Add, RealValue after save completes.
  332. }
  333. public Object Entity;
  334. public AutoGeneratedKeyType AutoGeneratedKeyType;
  335. public String PropertyName;
  336. public PropertyInfo Property {
  337. get {
  338. if (_property == null) {
  339. _property = Entity.GetType().GetProperty(PropertyName,
  340. BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  341. }
  342. return _property;
  343. }
  344. }
  345. public Object TempValue;
  346. public Object RealValue;
  347. private PropertyInfo _property;
  348. }
  349. // Types returned to javascript as Json.
  350. public class SaveResult {
  351. public List<Object> Entities;
  352. public List<KeyMapping> KeyMappings;
  353. public List<Object> Errors;
  354. }
  355. public class KeyMapping {
  356. public String EntityTypeName;
  357. public Object TempValue;
  358. public Object RealValue;
  359. }
  360. public class SaveError {
  361. public SaveError(IEnumerable<EntityError> entityErrors) {
  362. EntityErrors = entityErrors.ToList();
  363. }
  364. public List<EntityError> EntityErrors { get; protected set; }
  365. }
  366. public class EntityErrorsException : Exception {
  367. public EntityErrorsException(IEnumerable<EntityError> entityErrors) {
  368. EntityErrors = entityErrors.ToList();
  369. StatusCode = HttpStatusCode.Forbidden;
  370. }
  371. public EntityErrorsException(String message, IEnumerable<EntityError> entityErrors)
  372. : base(message) {
  373. EntityErrors = entityErrors.ToList();
  374. StatusCode = HttpStatusCode.Forbidden;
  375. }
  376. public HttpStatusCode StatusCode { get; set; }
  377. public List<EntityError> EntityErrors { get; protected set; }
  378. }
  379. public class EntityError {
  380. public String ErrorName;
  381. public String EntityTypeName;
  382. public Object[] KeyValues;
  383. public String PropertyName;
  384. public string ErrorMessage;
  385. }
  386. public class JsonPropertyFixupWriter : JsonTextWriter {
  387. public JsonPropertyFixupWriter(TextWriter textWriter)
  388. : base(textWriter) {
  389. _isDataType = false;
  390. }
  391. public override void WritePropertyName(string name) {
  392. if (name.StartsWith("@")) {
  393. name = name.Substring(1);
  394. }
  395. name = ToCamelCase(name);
  396. _isDataType = name == "type";
  397. base.WritePropertyName(name);
  398. }
  399. public override void WriteValue(string value) {
  400. if (_isDataType && !value.StartsWith("Edm.")) {
  401. base.WriteValue("Edm." + value);
  402. } else {
  403. base.WriteValue(value);
  404. }
  405. }
  406. private static string ToCamelCase(string s) {
  407. if (string.IsNullOrEmpty(s) || !char.IsUpper(s[0])) {
  408. return s;
  409. }
  410. string str = char.ToLower(s[0], CultureInfo.InvariantCulture).ToString((IFormatProvider)CultureInfo.InvariantCulture);
  411. if (s.Length > 1) {
  412. str = str + s.Substring(1);
  413. }
  414. return str;
  415. }
  416. private bool _isDataType;
  417. }
  418. }