2sxc: How to disable searching indexing - dotnetnuke

We are using a 2sxc module on an DNN Evoq install, there are multiple instances of 2sxc module app on a page which are inserted in lot of pages.
Can we disable search from indexing the content of one particular 2sxc module through its template file using razor code?

Yes you can :)
It's a bit tricky but each razor can modify what / how something is indexed, this is often needed when indexing List/Details-pages. Here's the starting point in the docs: https://github.com/2sic/2sxc/wiki/Razor-SexyContentWebPage.CustomizeSearch
I would try the following (haven't tried it myself, but should work)
#functions
{
/// <summary>
/// Populate the search - ensure that each entity has an own url/page
/// </summary>
/// <param name="searchInfos"></param>
/// <param name="moduleInfo"></param>
/// <param name="startDate"></param>
public override void CustomizeSearch(Dictionary<string, List<ToSic.SexyContent.Search.ISearchInfo>> searchInfos, DotNetNuke.Entities.Modules.ModuleInfo moduleInfo, DateTime startDate)
{
// clear the search-infos
searchInfos["Default"] = new List<ToSic.SexyContent.Search.ISearchInfo>();
}
}

Related

How do I get DNN Search results to display links to articles with a 2sxc app?

I have a 2sxc app that is a list of resources. It has a listing and each item goes to a details view that has a unique URL based on the title. (The URL field is a field in the content type). Something like this domain.com/resources/details/my-amazing-resource.
When a user searches the site for "amazing", the core DNN search results module displays the results of the app, including the "My Amazing Resource" item, but it doesn't actually link to domain.com/resources/details/my-amazing-resource. It just links to domain.com/resources/.
How can I make it so the search results actually point to the unique URL of the item in the app? Is this possible? Would DNNSharp Search Boost be better for this than the core DNN search module?
Its been over a year since I tinkered with it, but what it sounds like you are looking for requires coding. Dnn Search will get what it can from the 2sxc module automatically, but if you need to customize or improve what is being returned, then you need to CustomizeSearch() or CustomizeData() - I am not sure I have seen any decent examples, but I do know the FAQs App does this and must have a working example in it of some kind. Here is a place to stat in the 2sxc Docs,
CustomizeSearch().
I suggest examples in the Blog or News App.
Using Jeremy and Daniel's suggestions, I ultimately updated my _resourcelist.cshtml file to have code that looks like this:
#inherits ToSic.Sxc.Dnn.RazorComponent
#using ToSic.Razor.Blade;
#using ToSic.Eav.Run;
#using ToSic.Sxc.Dnn.Run;
#using ToSic.Sxc.Search;
#functions
{
/// <summary>
/// Populate the search - ensure that each entity has an own url/page
/// </summary>
/// <param name="searchInfos"></param>
/// <param name="moduleInfo"></param>
/// <param name="startDate"></param>
public override void CustomizeSearch(Dictionary<string, List<ISearchItem>> searchInfos, IContainer moduleInfo, DateTime beginDate)
{
foreach (var si in searchInfos["Default"])
{
// tell the search system what url it should use in the result
si.QueryString = "resource/" + AsDynamic(si.Entity).Link;
}
}
}

2sxc Blog app search only index page 1

Using 2sxc Blog app, the DNN only indexes whatever is on first page of Blog page.
Second page onwards is not indexed, hence doesn't show in search results.
Can anyone help?
This looks like a good question, and it's probably something we haven't thought about yet. Google doesn't care, but the internal search would probably "respect" the paging and only pick up the first page.
I can think of a few quick-fixes but they would be tricky to explain here. Please open an issue on the blog app on github.
Thanks alot #iJungleBoy for help.
For anyone else encountering this issues here's the solution:
Amend the visual query to create another stream example "SearchIndex"
Once thats done, amend the query within your template which gets all the list items and has paging.
#functions{
// Prepare the data - get all categories through the pipeline
public override void CustomizeData()
{
}
/// <summary>
/// Populate the search - ensure that each entity has an own url/page
/// </summary>
/// <param name="searchInfos"></param>
/// <param name="moduleInfo"></param>
/// <param name="startDate"></param>
public override void CustomizeSearch(Dictionary<string, List<ISearchInfo>> searchInfos, ModuleInfo moduleInfo, DateTime startDate)
{
foreach (var si in searchInfos["SearchIndex"])
{
si.QueryString = "post=" + AsDynamic(si.Entity).UrlKey;
}
}
}

How to add a new code-first migration with a newly-generated database?

I've enabled code-first migrations on my entity framework project, and have added several migrations which do things like rename tables. However, I have now deleted the database and caused entity framework to generate a brand new database based on my latest data model. If I try to run:
PM> Add-Migration TestMigration
... it tells me that I need to apply the existing migrations first. So I run:
PM> Update-Database
... but the trouble is that it's trying to update a database that doesn't need updating; it's already based on the latest data model. So I get an error when it tries to rename a table that now doesn't exist.
Is there some way I can indicate to data migrations that my database is up-to-date and doesn't need any migrations running on it? What should I do?
I've found a way to indicate that my database is up-to-date, and it's (unsurprisingly) based on modifying the __MigrationHistory table, which code-first migrations uses to determine which migrations to apply to the DB when you run Update-Database.
By the way, whilst researching this answer I came across a very nice reference for code-first migrations commands, which can be found at: http://dotnet.dzone.com/articles/ef-migrations-command
When the database is created from scratch automatically by EF, it will always put a single entry into the __MigrationHistory table, and that entry will have the MigrationId (currentDateTime)_InitialCreate. This represents the initial creation of the database that EF has just carried out. However, your migration history is not going to begin with that MigrationId because you will have started with something else.
To "fool" code-first migrations into thinking that you're on the latest migration, you need to delete that (currentDateTime)_InitialCreate entry from the __MigrationHistory table of the newly-created DB, and insert what would have been there if you still had the old DB which had had the migrations applied to it.
So, first delete everything from the newly-generated DB's __MigrationHistory table. Then, go into package manager console and run:
PM> Update-Database -Script
From the resulting SQL script, pull out all the lines beginning with:
INSERT INTO [__MigrationHistory]...
Then, run those INSERT statements within the context of the newly-created database. Check that each of those rows now exists in your __MigrationHistory table. When you next run:
PM> Update-Database
... you should get a message saying "No pending code-based migrations." Congratulations - you've fooled code-first migrations into thinking you're now on the latest migration, and you can continue where you left off adding new migrations from here.
I do think there should be some automated way of doing this built into EF code-first, though... maybe they should add something like:
PM> Update-Database -MigrationsTableOnly
... whereby it would clobber the existing entries in the migrations table, and just insert the new entries into migrations history for each migration defined in your project, but not actually try and run the migrations. Ah well.
UPDATE
I've found a way to automate this nicely, using a custom initializer's Seed method. Basically the Seed method deletes the existing migration history data when the DB is created, and inserts your migrations history. In my database context constructor, I register the custom initializer like so:
public class MyDatabaseContext : DbContext {
public MyDatabaseContext() : base() {
Database.SetInitializer(new MyDatabaseContextMigrationHistoryInitializer());
}
The custom initializer itself looks like this:
/// <summary>
/// This initializer clears the __MigrationHistory table contents created by EF code-first when it first
/// generates the database, and inserts all the migration history entries for migrations that have been
/// created in this project, indicating to EF code-first data migrations that the database is
/// "up-to-date" and that no migrations need to be run when "Update-Database" is run, because we're
/// already at the latest schema by virtue of the fact that the database has just been created from
/// scratch using the latest schema.
///
/// The Seed method needs to be updated each time a new migration is added with "Add-Migration". In
/// the package manager console, run "Update-Database -Script", and in the SQL script which is generated,
/// find the INSERT statement that inserts the row for that new migration into the __MigrationHistory
/// table. Add that INSERT statement as a new "ExecuteSqlCommand" to the end of the Seed method.
/// </summary>
public class MyDatabaseContextMigrationHistoryInitializer : CreateDatabaseIfNotExists<MyDatabaseContext> {
/// <summary>
/// Sets up this context's migration history with the entries for all migrations that have been created in this project.
/// </summary>
/// <param name="context">The context of the database in which the seed code is to be executed.</param>
protected override void Seed(MyDatabaseContext context) {
// Delete existing content from migration history table, and insert our project's migrations
context.Database.ExecuteSqlCommand("DELETE FROM __MigrationHistory");
context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210091606260_InitialCreate', 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4AF54AD7E074A..., '5.0.0.net40')");
context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210102218467_MakeConceptUserIdNullable', 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4..., '5.0.0.net40')");
context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210231418163_ChangeDateTimesToDateTimeOffsets', 0x1F8B0800000000000400ECBD07601C499625262F6D..., '5.0.0.net40')");
context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210251833252_AddConfigSettings', 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4AF54AD7E..., '5.0.0.net40')");
context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210260822485_RenamingOfSomeEntities', 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4AF5..., '5.0.0.net40')");
}
}
Go to MigrationHistory table in sql server (under system folder)
it has row for your migration and db hash in it which must be the same with one in your migration file, just copy it from db to file.
In other words you need to sync your MigrationHistory table with actual migrations.
This implementation does not need to manually maintain records to be inserted to __MigrationHistory. Migrations are determined from assembly specified.
Maybe this helps.
My thanks goes to #Jez for initial idea.
/// <summary>
/// An implementation of IDatabaseInitializer that will:
/// 1. recreate database only if the database does not exist
/// 2. actualize __MigrationHistory to match current model state (i.e. latest migration)
/// </summary>
/// <typeparam name="TContext">The type of the context.</typeparam>
public class CreateDatabaseIfNotExistsAndMigrateToLatest<TContext> : CreateDatabaseIfNotExists<TContext>
where TContext : DbContext
{
private readonly Assembly migrationsAssembly;
/// <summary>
/// Gets the migration metadata for types retrieved from <paramref name="assembly"/>. Types must implement <see cref="IMigrationMetadata"/>.
/// </summary>
/// <param name="assembly">The assembly.</param>
/// <returns></returns>
private static IEnumerable<IMigrationMetadata> GetMigrationMetadata(Assembly assembly)
{
var types = assembly.GetTypes().Where(t => typeof(IMigrationMetadata).IsAssignableFrom(t));
var migrationMetadata = new List<IMigrationMetadata>();
foreach (var type in types)
{
migrationMetadata.Add(
(IMigrationMetadata)Activator.CreateInstance(type));
}
return migrationMetadata.OrderBy(m => m.Id);
}
/// <summary>
/// Gets the provider manifest token.
/// </summary>
/// <param name="db">The db.</param>
/// <returns></returns>
private static string GetProviderManifestToken(TContext db)
{
var connection = db.Database.Connection;
var token = DbProviderServices.GetProviderServices(connection).GetProviderManifestToken(connection);
return token;
}
/// <summary>
/// Gets the migration SQL generator. Currently it is <see cref="SqlServerMigrationSqlGenerator"/>.
/// </summary>
/// <returns></returns>
private static MigrationSqlGenerator GetMigrationSqlGenerator()
{
return new SqlServerMigrationSqlGenerator();
}
/// <summary>
/// Creates the operation for inserting into migration history. Operation is created for one <paramref name="migrationMetadatum"/>.
/// </summary>
/// <param name="migrationMetadatum">The migration metadatum.</param>
/// <returns></returns>
private static InsertHistoryOperation CreateInsertHistoryOperation(IMigrationMetadata migrationMetadatum)
{
var model = Convert.FromBase64String(migrationMetadatum.Target);
var op = new InsertHistoryOperation(
"__MigrationHistory",
migrationMetadatum.Id,
model,
null);
return op;
}
/// <summary>
/// Generates the SQL statements for inserting migration into history table.
/// </summary>
/// <param name="generator">The generator.</param>
/// <param name="op">The operation.</param>
/// <param name="token">The token.</param>
/// <returns></returns>
private static IEnumerable<MigrationStatement> GenerateInsertHistoryStatements(
MigrationSqlGenerator generator,
InsertHistoryOperation op,
string token)
{
return generator.Generate(new[] { op }, token);
}
/// <summary>
/// Runs the SQL statements on database specified by <paramref name="db"/> (<see cref="DbContext.Database"/>).
/// </summary>
/// <param name="statements">The statements.</param>
/// <param name="db">The db.</param>
private static void RunSqlStatements(IEnumerable<MigrationStatement> statements, TContext db)
{
foreach (var statement in statements)
{
db.Database.ExecuteSqlCommand(statement.Sql);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="CreateDatabaseIfNotExistsAndMigrateToLatest{TContext}"/> class.
/// </summary>
/// <param name="migrationsAssembly">The migrations assembly.</param>
public CreateDatabaseIfNotExistsAndMigrateToLatest(Assembly migrationsAssembly)
{
this.migrationsAssembly = migrationsAssembly;
}
protected override void Seed(TContext context)
{
base.Seed(context);
// Get migration metadata for migrationAssembly
var migrationMetadata = GetMigrationMetadata(migrationsAssembly);
// Crate DbContext
var db = Activator.CreateInstance<TContext>();
// Remove newly created record in __MigrationHistory
db.Database.ExecuteSqlCommand("DELETE FROM __MigrationHistory");
// Get provider manifest token
var token = GetProviderManifestToken(db);
// Get sql generator
var generator = GetMigrationSqlGenerator();
foreach (var migrationMetadatum in migrationMetadata)
{
// Create history operation
var op = CreateInsertHistoryOperation(migrationMetadatum);
// Generate history insert statements
var statements = GenerateInsertHistoryStatements(generator, op, token);
// Run statements (SQL) over database (db)
RunSqlStatements(statements, db);
}
}
}

Can I change the T4 for a Silverlight WCF Data Service ServiceReference?

I have a WCF Data Service in my web app. I added a service reference using the "Add New Service Reference" command in my Silverlight application. I was looking at the Reference.cs file VS generates for me and noticed the setters don't check for a change before calling OnPropertyChanged. I'd like to change this behavior. Can I overrride the T4 template without having to override all the code generation?
If it's possible how would I go about doing it?
original generated code
/// <summary>
/// There are no comments for Property Title in the schema.
/// </summary>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Services.Design", "1.0.0")]
public string Title
{
get
{
return this._Title;
}
set
{
this.OnTitleChanging(value);
this._Title = value;
this.OnTitleChanged();
this.OnPropertyChanged("Title");
}
}
Desired change:
/// <summary>
/// There are no comments for Property Title in the schema.
/// </summary>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Services.Design", "1.0.0")]
public string Title
{
get
{
return this._Title;
}
set
{
// change to
if(this._Title != value) {
this.OnTitleChanging(value);
this._Title = value;
this.OnTitleChanged();
this.OnPropertyChanged("Title");
}
}
}
Unfortunately the Add Service Reference for WCF Data Services doesn't use T4 yet. So there's no easy way to do this. Feel free to vote for the feature here: http://blogs.msdn.com/b/astoriateam/archive/2010/09/10/what-do-you-want-to-see-added-changed-in-wcf-data-services.aspx

Get the Windows Phone 7 Application Title from Code

I want to access the Title value that is stored in the WMAppManifest.xml file from my ViewModel code. This is the same application title that is set through the project properties.
Is there a way to access this from code using something like App.Current?
Look at the source code for WP7DataCollector.GetAppAttribute() in the Microsoft Silverlight Analytics Framework. GetAppAttribute("Title") will do it.
/// <summary>
/// Gets an attribute from the Windows Phone App Manifest App element
/// </summary>
/// <param name="attributeName">the attribute name</param>
/// <returns>the attribute value</returns>
private static string GetAppAttribute(string attributeName)
{
string appManifestName = "WMAppManifest.xml";
string appNodeName = "App";
var settings = new XmlReaderSettings();
settings.XmlResolver = new XmlXapResolver();
using (XmlReader rdr = XmlReader.Create(appManifestName, settings))
{
rdr.ReadToDescendant(appNodeName);
if (!rdr.IsStartElement())
{
throw new System.FormatException(appManifestName + " is missing " + appNodeName);
}
return rdr.GetAttribute(attributeName);
}
}
This last answer seems overly complicated to me ; you could have simply done something like:
string name = "";
var executingAssembly = System.Reflection.Assembly.GetExecutingAssembly();
var customAttributes = executingAssembly.GetCustomAttributes(typeof(System.Reflection.AssemblyTitleAttribute), false);
if (customAttributes != null)
{
var assemblyName = customAttributes[0] as System.Reflection.AssemblyTitleAttribute;
name = assemblyName.Title;
}
I have used Michael S. Scherotter his excellent code sample to work it out to a fully working code sample:
using System.Xml;
namespace KoenZomers.WinPhone.Samples
{
/// <summary>
/// Allows application information to be retrieved
/// </summary>
public static class ApplicationInfo
{
#region Constants
/// <summary>
/// Filename of the application manifest contained within the XAP file
/// </summary>
private const string AppManifestName = "WMAppManifest.xml";
/// <summary>
/// Name of the XML element containing the application information
/// </summary>
private const string AppNodeName = "App";
#endregion
#region Properties
/// <summary>
/// Gets the application title
/// </summary>
public static string Title
{
get { return GetAppAttribute("Title"); }
}
/// <summary>
/// Gets the application description
/// </summary>
public static string Description
{
get { return GetAppAttribute("Description"); }
}
/// <summary>
/// Gets the application version
/// </summary>
public static string Version
{
get { return GetAppAttribute("Version"); }
}
/// <summary>
/// Gets the application publisher
/// </summary>
public static string Publisher
{
get { return GetAppAttribute("Publisher"); }
}
/// <summary>
/// Gets the application author
/// </summary>
public static string Author
{
get { return GetAppAttribute("Author"); }
}
#endregion
#region Methods
/// <summary>
/// Gets an attribute from the Windows Phone App Manifest App element
/// </summary>
/// <param name="attributeName">the attribute name</param>
/// <returns>the attribute value</returns>
private static string GetAppAttribute(string attributeName)
{
var settings = new XmlReaderSettings {XmlResolver = new XmlXapResolver()};
using (var rdr = XmlReader.Create(AppManifestName, settings))
{
rdr.ReadToDescendant(AppNodeName);
// Return the value of the requested XML attribute if found or NULL if the XML element with the application information was not found in the application manifest
return !rdr.IsStartElement() ? null : rdr.GetAttribute(attributeName);
}
}
#endregion
}
}
Only the first two answers are correct in scope of the original question. And the second is certainly not over complicated. Wrapping the helper method with a class for each possible attribute is good object orientated development and exactly what Microsoft do all over the framework, e.g. settings designer files generated by Visual Studio.
I'd recommend using the first if you just want one specific property, the second if you want more. Should be part of the SDK really. We're trying to read the WMAppManifest.xml here not the AssemblyInfo so standard assembly reflection metadata is no good.
By the way, if you really want to get the product name from the assembly attributes (not WPAppManifest.xml) then the last sample was reading the wrong attribute! Use the AssemblyProductAttribute not the AssemblyTitleAttribute. The assembly title is really the file title, by default the same as the assembly file name (e.g. MyCompany.MyProduct.WinPhone7App) whereas the product will typically be something like the properly formatted "title" of your app in the store (e.g. "My Product"). It may not even be up-to-date after using the VS properties page, so you should check that.
I use AssemblyInfo reflection for all other application types to show the official product name and build version on an about page, it's certainly correct for that. But for these special phone app types the store manifest has more importance and other attributes you may need.
The problem with all of those answers is that they have to read the file every single time it is accessed. This is bad for performance as there are battery issues to consider if you use it frequently. Koen was closer to a proper solution, but his design still went back to the file every time you wanted to access the value.
The solution below is a one-and-done read of the file. Since it is not likely to change, there is no reason to keep going back to it. The attributes are read as the static class is initialized, with minimal fuss.
I created this Gist to demonstrate.
HTH!

Resources