Exemple #1
0
        /// <summary>
        /// Handles when a model binding error occurs
        /// </summary>
        public void Handle(ModelBindingErrorNotification notification)
        {
            ModelsBuilderAssemblyAttribute sourceAttr = notification.SourceType.Assembly.GetCustomAttribute <ModelsBuilderAssemblyAttribute>();
            ModelsBuilderAssemblyAttribute modelAttr  = notification.ModelType.Assembly.GetCustomAttribute <ModelsBuilderAssemblyAttribute>();

            // if source or model is not a ModelsBuider type...
            if (sourceAttr == null || modelAttr == null)
            {
                // if neither are ModelsBuilder types, give up entirely
                if (sourceAttr == null && modelAttr == null)
                {
                    return;
                }

                // else report, but better not restart (loops?)
                notification.Message.Append(" The ");
                notification.Message.Append(sourceAttr == null ? "view model" : "source");
                notification.Message.Append(" is a ModelsBuilder type, but the ");
                notification.Message.Append(sourceAttr != null ? "view model" : "source");
                notification.Message.Append(" is not. The application is in an unstable state and should be restarted.");
                return;
            }

            // both are ModelsBuilder types
            var pureSource = sourceAttr.IsInMemory;
            var pureModel  = modelAttr.IsInMemory;

            if (sourceAttr.IsInMemory || modelAttr.IsInMemory)
            {
                if (pureSource == false || pureModel == false)
                {
                    // only one is pure - report, but better not restart (loops?)
                    notification.Message.Append(pureSource
                        ? " The content model is in memory generated, but the view model is not."
                        : " The view model is in memory generated, but the content model is not.");
                    notification.Message.Append(" The application is in an unstable state and should be restarted.");
                }
                else
                {
                    // both are pure - report, and if different versions, restart
                    // if same version... makes no sense... and better not restart (loops?)
                    Version sourceVersion = notification.SourceType.Assembly.GetName().Version;
                    Version modelVersion  = notification.ModelType.Assembly.GetName().Version;
                    notification.Message.Append(" Both view and content models are in memory generated, with ");
                    notification.Message.Append(sourceVersion == modelVersion
                        ? "same version. The application is in an unstable state and should be restarted."
                        : "different versions. The application is in an unstable state and should be restarted.");
                }
            }
        }
Exemple #2
0
        // This is NOT thread safe but it is only called from within a lock
        private Assembly GetModelsAssembly(bool forceRebuild)
        {
            if (!Directory.Exists(_pureLiveDirectory.Value))
            {
                Directory.CreateDirectory(_pureLiveDirectory.Value);
            }

            IList <TypeModel> typeModels = UmbracoServices.GetAllTypes();
            var currentHash    = TypeModelHasher.Hash(typeModels);
            var modelsHashFile = Path.Combine(_pureLiveDirectory.Value, "models.hash");
            var modelsSrcFile  = Path.Combine(_pureLiveDirectory.Value, "models.generated.cs");
            var projFile       = Path.Combine(_pureLiveDirectory.Value, "all.generated.cs");
            var dllPathFile    = Path.Combine(_pureLiveDirectory.Value, "all.dll.path");

            // caching the generated models speeds up booting
            // currentHash hashes both the types & the user's partials
            if (!forceRebuild)
            {
                _logger.LogDebug("Looking for cached models.");
                if (File.Exists(modelsHashFile) && File.Exists(projFile))
                {
                    var cachedHash = File.ReadAllText(modelsHashFile);
                    if (currentHash != cachedHash)
                    {
                        _logger.LogDebug("Found obsolete cached models.");
                        forceRebuild = true;
                    }

                    // else cachedHash matches currentHash, we can try to load an existing dll
                }
                else
                {
                    _logger.LogDebug("Could not find cached models.");
                    forceRebuild = true;
                }
            }

            Assembly assembly;

            if (!forceRebuild)
            {
                // try to load the dll directly (avoid rebuilding)
                //
                // ensure that the .dll file does not have a corresponding .dll.delete file
                // as that would mean the the .dll file is going to be deleted and should not
                // be re-used - that should not happen in theory, but better be safe
                if (File.Exists(dllPathFile))
                {
                    var dllPath = File.ReadAllText(dllPathFile);

                    _logger.LogDebug($"Cached models dll at {dllPath}.");

                    if (File.Exists(dllPath) && !File.Exists(dllPath + ".delete"))
                    {
                        assembly = ReloadAssembly(dllPath);

                        ModelsBuilderAssemblyAttribute attr = assembly.GetCustomAttribute <ModelsBuilderAssemblyAttribute>();
                        if (attr != null && attr.IsInMemory && attr.SourceHash == currentHash)
                        {
                            // if we were to resume at that revision, then _ver would keep increasing
                            // and that is probably a bad idea - so, we'll always rebuild starting at
                            // ver 1, but we remember we want to skip that one - so we never end up
                            // with the "same but different" version of the assembly in memory
                            _skipver = assembly.GetName().Version.Revision;

                            _logger.LogDebug("Loading cached models (dll).");
                            return(assembly);
                        }

                        _logger.LogDebug("Cached models dll cannot be loaded (invalid assembly).");
                    }
                    else if (!File.Exists(dllPath))
                    {
                        _logger.LogDebug("Cached models dll does not exist.");
                    }
                    else if (File.Exists(dllPath + ".delete"))
                    {
                        _logger.LogDebug("Cached models dll is marked for deletion.");
                    }
                    else
                    {
                        _logger.LogDebug("Cached models dll cannot be loaded (why?).");
                    }
                }

                // must reset the version in the file else it would keep growing
                // loading cached modules only happens when the app restarts
                var   text  = File.ReadAllText(projFile);
                Match match = s_assemblyVersionRegex.Match(text);
                if (match.Success)
                {
                    text = text.Replace(match.Value, "AssemblyVersion(\"0.0.0." + _ver + "\")");
                    File.WriteAllText(projFile, text);
                }

                _ver++;
                try
                {
                    var assemblyPath = GetOutputAssemblyPath(currentHash);
                    RoslynCompiler.CompileToFile(projFile, assemblyPath);
                    assembly = ReloadAssembly(assemblyPath);
                    File.WriteAllText(dllPathFile, assembly.Location);
                    File.WriteAllText(modelsHashFile, currentHash);
                    TryDeleteUnusedAssemblies(dllPathFile);
                }
                catch
                {
                    ClearOnFailingToCompile(dllPathFile, modelsHashFile, projFile);
                    throw;
                }

                _logger.LogDebug("Loading cached models (source).");
                return(assembly);
            }

            // need to rebuild
            _logger.LogDebug("Rebuilding models.");

            // generate code, save
            var code = GenerateModelsCode(typeModels);
            // add extra attributes,
            //  IsLive=true helps identifying Assemblies that contain live models
            //  AssemblyVersion is so that we have a different version for each rebuild
            var ver = _ver == _skipver ? ++_ver : _ver;

            _ver++;
            string mbAssemblyDirective = $@"[assembly:ModelsBuilderAssembly(IsInMemory = true, SourceHash = ""{currentHash}"")]
[assembly:System.Reflection.AssemblyVersion(""0.0.0.{ver}"")]";

            code = code.Replace("//ASSATTR", mbAssemblyDirective);
            File.WriteAllText(modelsSrcFile, code);

            // generate proj, save
            var projFiles = new Dictionary <string, string>
            {
                { "models.generated.cs", code }
            };
            var proj = GenerateModelsProj(projFiles);

            File.WriteAllText(projFile, proj);

            // compile and register
            try
            {
                var assemblyPath = GetOutputAssemblyPath(currentHash);
                RoslynCompiler.CompileToFile(projFile, assemblyPath);
                assembly = ReloadAssembly(assemblyPath);
                File.WriteAllText(dllPathFile, assemblyPath);
                File.WriteAllText(modelsHashFile, currentHash);
                TryDeleteUnusedAssemblies(dllPathFile);
            }
            catch
            {
                ClearOnFailingToCompile(dllPathFile, modelsHashFile, projFile);
                throw;
            }

            _logger.LogDebug("Done rebuilding.");
            return(assembly);
        }