How to initialize RavenDb database with bundle programatically? - ravendb

I have this code it works well EXCEPT I want to ensure that the expiration bundle is in the database and I dont want to create this database manually. What is the correct way to initialize a database with the expiration bundle enabled?
_documentStore = new DocumentStore()
{
Url = SettingsManager.RavenDbUrl,
DefaultDatabase = SettingsManager.RavenDbDatabaseName
};
_documentStore.Initialize();

Well I couldnt find docs and I couldnt find help so I cobbled this together:
static class RavenDbExtensions
{
/// <summary>
/// Ensure a bundle is activated
/// </summary>
/// <param name="documentStore"></param>
/// <param name="bundleName"></param>
/// <param name="databaseName"></param>
public static void ActivateBundle(this IDocumentStore documentStore, string bundleName, string databaseName)
{
using (var session = documentStore.OpenSession())
{
var databaseDocument = session.Load<DatabaseDocument>("Raven/Databases/" + databaseName);
var settings = databaseDocument.Settings;
var activeBundles = settings.ContainsKey(Constants.ActiveBundles) ? settings[Constants.ActiveBundles] : null;
if (string.IsNullOrEmpty(activeBundles))
settings[Constants.ActiveBundles] = bundleName;
else if (!activeBundles.Split(new char[]{';'}).Contains(bundleName, StringComparer.OrdinalIgnoreCase))
settings[Constants.ActiveBundles] = activeBundles + ";" + bundleName;
session.SaveChanges();
}
}
}
Then I do this little dance when initializing the document store. It seems to work well. It isnt clear whether the bundle is named Expiration or DocumentExpiration so I try both it doesnt crash and it seems to fold in the expiration functionality I need.
_documentStore = new DocumentStore()
{
Url = SettingsManager.RavenDbUrl
};
_documentStore.Initialize();
_documentStore.ActivateBundle("Expiration", Assembly.GetExecutingAssembly().GetName().Name);
_documentStore.ActivateBundle("DocumentExpiration", Assembly.GetExecutingAssembly().GetName().Name);
_documentStore.DefaultDatabase = Assembly.GetExecutingAssembly().GetName().Name;

Related

Remove route from RouteCollection in Asp.Net Core and add new with same route name (nopCommerce-4.00)

I want to remove existing route from RouteCollection and want to add new route with same route name in nopCommerce 4.00 via plugin
Existing route name:
//home page
routeBuilder.MapLocalizedRoute("HomePage", "",
new { controller = "Home", action = "Index" });
I Want to replace it with
routeBuilder.MapLocalizedRoute("HomePage", "",
new { controller = "CustomPage", action = "Homepage" });
I tried several ways but not get any luck.
In my case, I have to replace the robots.txt generation.
I created a new public controller in my plugin, and I copy the original action here:
public class MiscCommonController : BasePublicController
{
#region Fields
private readonly ICommonModelFactory _commonModelFactory;
#endregion Fields
#region Ctor
public MiscCommonController(
ICommonModelFactory commonModelFactory
)
{
this._commonModelFactory = commonModelFactory;
}
#endregion Ctor
#region Methods
//robots.txt file
//available even when a store is closed
[CheckAccessClosedStore(true)]
//available even when navigation is not allowed
[CheckAccessPublicStore(true)]
public virtual IActionResult RobotsTextFile()
{
var robotsFileContent = _commonModelFactory.PrepareRobotsTextFile();
return Content(robotsFileContent, MimeTypes.TextPlain);
}
#endregion Methods
}
After this I create a RouteProvider for my plugin, and I replaced the original route to my own one.
public partial class RouteProvider : IRouteProvider
{
/// <summary>
/// Gets a priority of route provider
/// </summary>
public int Priority => -1;
/// <summary>
/// Register routes
/// </summary>
/// <param name="routeBuilder">Route builder</param>
public void RegisterRoutes(IRouteBuilder routeBuilder)
{
Route route = null;
foreach (Route item in routeBuilder.Routes)
{
if (item.Name == "robots.txt")
{
route = item;
break;
}
}
if (route != null) routeBuilder.Routes.Remove(route);
routeBuilder.MapRoute(
"robots.txt",
"robots.txt",
new { controller = "MiscCommon", action = "RobotsTextFile" }
);
}
}
That's all.
After this implementation, the routing works fine, and the get request landed in my own controller, which is act like the original.
Now, I can replace the generation logic with my own.
I hope it helps.
in the RouteProvider.cs of your plugin write these codes (based on your names):
var lastExistingRoute= routeBuilder.Routes.FirstOrDefault(x => ((Route)x).Name == "HomePage");
routeBuilder.Routes.Remove(lastExistingRoute);
routeBuilder.MapRoute("HomePage", "",
new { controller = "CustomPage", action = "Homepage", });
and the below codes worked for myself version 4.20:
var lastDownloadRoute=routeBuilder.Routes.FirstOrDefault(x => ((Route)x).Name == "GetDownload");
routeBuilder.Routes.Remove(lastDownloadRoute);
routeBuilder.MapRoute("GetDownload", "download/getdownload/{guid}/{agree?}",
new { controller = "AzTechProduct", action = "GetPayed", });
There are two potential ways to deal with this in nopCommerce 4.3 that I see with a quick examination of the code.
First, you could create an IRouteProvider, add your route that has the same signature as the one you wish to 'delete' and make sure the Priority on the provider is greater than 1.
Doing this will basically override the default route built into Nop. This is my preferred method.
public partial class RouteProvider: IRouteProvider
{
public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder)
{
var pattern = string.Empty;
if (DataSettingsManager.DatabaseIsInstalled)
{
var localizationSettings = endpointRouteBuilder.ServiceProvider.GetRequiredService<LocalizationSettings>();
if (localizationSettings.SeoFriendlyUrlsForLanguagesEnabled)
{
var langservice = endpointRouteBuilder.ServiceProvider.GetRequiredService<ILanguageService>();
var languages = langservice.GetAllLanguages().ToList();
pattern = "{language:lang=" + languages.FirstOrDefault().UniqueSeoCode + "}/";
}
}
// Handle the standard request
endpointRouteBuilder.MapControllerRoute("Wishlist", pattern + "wishlist/{customerGuid?}",
new { controller = "MyShoppingCart", action = "Wishlist" });
return;
}
public int Priority => 100;
}
The key to the code above is the Priority value. This route will get added to the list first and will therefore take precedence over the default route. Using this technique eliminates the need to delete the default route.
The second possible method turns out to not work because the endpointRouteBuilder.DataSources[n].Endpoints collection is read only. So, as far as I know, you can't remove mappings from that list after they have been added.

OData service with multiple routes while using unbound functions

Does anyone know how to get OData v4 hosted in a .NET service to work with multiple routes?
I have the following:
config.MapODataServiceRoute("test1", "test1", GetEdmModelTest1());
config.MapODataServiceRoute("test2", "test2", GetEdmModelTest2());
Each of the GetEdmModel methods have mapped objects.
I can get to the service as following (this is working fine):
http://testing.com/test1/objects1()
http://testing.com/test2/objects2()
But if I try to call a function like the following (will not work):
[HttpGet]
[ODataRoute("test1/TestFunction1()")]
public int TestFunction1()
{ return 1; }
It will throw the following error:
The path template 'test1/TestFunction1()' on the action 'TestFunction1' in controller 'Testing' is not a valid OData path template. Resource not found for the segment 'test1'.
Yet if I remove the "MapODataServiceRoute" for "test2" so there is only one route, it all works.
How do I get this to work with multiple routes?
** I have posted a full example of the issue at the following **
https://github.com/OData/WebApi/issues/1223
** I have tried the OData version sample listed below with the following issues **
https://github.com/OData/ODataSamples/tree/master/WebApi/v4/ODataVersioningSample
I have tried the "OData Version" example before and it did not work.
It seems that unbound (unbound is the goal) does not follow the same routing rules are normal service calls.
Ex. If you download the "OData Version" example and do the following.
In V1 -> WebApiConfig.cs add
builder.Function(nameof(Controller.ProductsV1Controller.Test)).Returns<string>();
In V2 -> WebApiConfig.cs add
builder.Function(nameof(Controller.ProductsV2Controller.Test)).Returns<string>();
In V1 -> ProductsV1Controller.cs add
[HttpGet]
[ODataRoute("Test()")]
public string Test()
{ return "V1_Test"; }
In V2 -> ProductsV2Controller.cs add
[HttpGet]
[ODataRoute("Test()")]
public string Test()
{ return "V2_Test"; }
Now call it by this. " /versionbyroute/v1/Test() " and you will get "V2_Test"
The problem is that "GetControllerName" does not know how to get the controller when it is using unbound functions / actions.
This is why most sample code I have found fails when trying to "infer" the controller.
Have a look at OData Versioning Sample for a primer.
The key point of trouble is usually that the DefaultHttpControllerSelector maps controllers by local name, not fullname/namespace.
If your entity types and therefore controller names are unique across both EdmModels you will not have to do anything special, it should just work out of the box. The above sample takes advantage of this concept by forcing you to inject a string value into the physical names of the controller classes to make them unique and then in the ODataVersionControllerSelector GetControllerName is overridden to maps the incoming route to the customised controller names
If unique names for the controllers seems to hard, and you would prefer to use the full namespace (meaning your controller names logic remains standard) then you can of course implement your own logic to select the specific controller class instance when overriding DefaultHttpControllerSelector. simply override SelectController instead. This method will need to return an instance of HttpControllerDescriptor which is a bit more involved than the sample.
To show you what I mean, I will post the solution to a requirement from an older project, that was a little bit different to yours. I have a single WebAPI project that manages access to multiple databases, these databases have similar schema, many Entity names are the same which means that those controller classes will have the same names. The controllers are structured by folders/namespaces such that there is a root folder called DB, then there is a folder for each database, then the controllers are in there.
You can see that this project has many different schemas, they effectively map to versions of an evolving solution, the non-DB namespaces in this image are a mix of OData v4, v3 and standard REST apis. It is possible to get all these beasts to co-exist ;)
This override of the HttpControllerSelector inspects the runtime once to cache a list of all the controller classes, then maps the incoming route requests by matching the route prefix to the correct controller class.
/// <summary>
/// Customised controller for intercepting traffic for the DB Odata feeds.
/// Any route that is not prefixed with ~/DB/ will not be intercepted or processed via this controller
/// <remarks>Will instead be directed to the base class</remarks>
/// </summary>
public class DBODataHttpControllerSelector : DefaultHttpControllerSelector
{
private readonly HttpConfiguration _configuration;
public DBODataHttpControllerSelector(HttpConfiguration config)
: base(config)
{
_configuration = config;
}
// From: http://www.codeproject.com/Articles/741326/Introduction-to-Web-API-Versioning
private Dictionary<string, HttpControllerDescriptor> _controllerMap = null;
private List<string> _duplicates = new List<string>();
/// <summary>
/// Because we are interested in supporting nested namespaces similar to MVC "Area"s we need to
/// Index our available controller classes by the potential url segments that might be passed in
/// </summary>
/// <returns></returns>
private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
{
if(_controllerMap != null)
return _controllerMap;
_controllerMap = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
// Create a lookup table where key is "namespace.controller". The value of "namespace" is the last
// segment of the full namespace. For example:
// MyApplication.Controllers.V1.ProductsController => "V1.Products"
IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
foreach (Type t in controllerTypes)
{
var segments = t.Namespace.Split(Type.Delimiter);
// For the dictionary key, strip "Controller" from the end of the type name.
// This matches the behavior of DefaultHttpControllerSelector.
var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}", segments[segments.Length - 2], segments[segments.Length - 1], controllerName);
// Check for duplicate keys.
if (_controllerMap.Keys.Contains(key))
{
_duplicates.Add(key);
}
else
{
_controllerMap[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
}
}
// Remove any duplicates from the dictionary, because these create ambiguous matches.
// For example, "Foo.V1.ProductsController" and "Bar.V1.ProductsController" both map to "v1.products".
// CS: Ahem... thats why I've opted to go 3 levels of depth to key name, but this still applies if the duplicates are there again
foreach (string s in _duplicates)
{
_controllerMap.Remove(s);
}
return _controllerMap;
}
/// <summary>
/// Because we are interested in supporting nested namespaces we want the full route
/// to match to the full namespace (or at least the right part of it)
/// </summary>
/// <returns></returns>
private Dictionary<string, HttpControllerDescriptor> _fullControllerMap = null;
private Dictionary<string, HttpControllerDescriptor> InitializeFullControllerDictionary()
{
if(_fullControllerMap != null)
return _fullControllerMap;
_fullControllerMap = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
// Create a lookup table where key is "namespace.controller". The value of "namespace" is the last
// segment of the full namespace. For example:
// MyApplication.Controllers.V1.ProductsController => "V1.Products"
IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
foreach (Type t in controllerTypes)
{
var segments = t.Namespace.Split(Type.Delimiter);
// For the dictionary key, strip "Controller" from the end of the type name.
// This matches the behavior of DefaultHttpControllerSelector.
var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
var key = t.FullName;// t.Namespace + "." + controllerName;
_fullControllerMap[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
}
return _fullControllerMap;
}
/// <summary>
/// Select the controllers with a simulated MVC area sort of functionality, but only for the ~/DB/ route
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public override System.Web.Http.Controllers.HttpControllerDescriptor SelectController(System.Net.Http.HttpRequestMessage request)
{
string rootPath = "db";
IHttpRouteData routeData = request.GetRouteData();
string[] uriSegments = request.RequestUri.LocalPath.Split('/');
if (uriSegments.First().ToLower() == rootPath || uriSegments[1].ToLower() == rootPath)
{
#region DB Route Selector
// If we can find a known api and a controller, then redirect to the correct controller
// Otherwise allow the standard select to work
string[] knownApis = new string[] { "tms", "srg", "cumulus" };
// Get variables from the route data.
/* support version like this:
* config.Routes.MapODataRoute(
routeName: "ODataDefault",
routePrefix: "{version}/{area}/{controller}",
model: model);
object versionName = null;
routeData.Values.TryGetValue("version", out versionName);
object apiName = null;
routeData.Values.TryGetValue("api", out apiName);
object controllerName = null;
routeData.Values.TryGetValue("controller", out controllerName);
* */
// CS: we'll just use the local path AFTER the root path
// db/tms/contact
// db/srg/contact
// Implicity parse this as
// db/{api}/{controller}
// so [0] = ""
// so [1] = "api"
// so [2] = "version" (optional)
// so [2 or 3] = "controller"
if (uriSegments.Length > 3)
{
string apiName = uriSegments[2];
if (knownApis.Contains(string.Format("{0}", apiName).ToLower()))
{
string version = "";
string controllerName = uriSegments[3];
if (controllerName.ToLower().StartsWith("v")
// and the rest of the name is numeric
&& !controllerName.Skip(1).Any(c => !Char.IsNumber(c))
)
{
version = controllerName;
controllerName = uriSegments[4];
}
// if the route has an OData item selector (#) then this needs to be trimmed from the end.
if (controllerName.Contains('('))
controllerName = controllerName.Substring(0, controllerName.IndexOf('('));
string fullName = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}", apiName, version, controllerName).Replace("..", ".");
// Search for the controller.
// _controllerTypes is a list of HttpControllerDescriptors
var descriptors = InitializeControllerDictionary().Where(t => t.Key.EndsWith(fullName, StringComparison.OrdinalIgnoreCase)).ToList();
if (descriptors.Any())
{
var descriptor = descriptors.First().Value;
if (descriptors.Count > 1)
{
descriptor = null;
// Assume that the version was missing, and we have implemented versioning for that controller
// If there is a row with no versioning, so no v1, v2... then use that
// if all rows are versioned, use the highest version
if (descriptors.Count(d => d.Key.Split('.').Length == 2) == 1)
descriptor = descriptors.First(d => d.Key.Split('.').Length == 2).Value;
else if (descriptors.Count(d => d.Key.Split('.').Length > 2) == descriptors.Count())
descriptor = descriptors
.Where(d => d.Key.Split('.').Length > 2)
.OrderByDescending(d => d.Key.Split('.')[1])
.First().Value;
if (descriptor == null)
throw new HttpResponseException(
request.CreateErrorResponse(HttpStatusCode.InternalServerError,
"Multiple controllers were found that match this un-versioned request."));
}
if (descriptor != null)
return descriptor;
}
if (_duplicates.Any(d => d.ToLower() == fullName.ToLower()))
throw new HttpResponseException(
request.CreateErrorResponse(HttpStatusCode.InternalServerError,
"Multiple controllers were found that match this request."));
}
}
#endregion DB Route Selector
}
else
{
// match on class names that match the route.
// So if the route is odata.tms.testController
// Then the class name must also match
// Add in an option to doing a string mapping, so that
// route otms can mapp to odata.tms
// TODO: add any other custom logic for selecting the controller that you want, alternatively try this style syntax in your route config:
//routes.MapRoute(
// name: "Default",
// url: "{controller}/{action}/{id}",
// defaults: new { controller = "Home", action = "RegisterNow", id = UrlParameter.Optional },
// namespaces: new[] { "YourCompany.Controllers" }
//);
// Because controller path mapping might be controller/navigationproperty/action
// We need to check for the following matches:
// controller.navigationproperty.actionController
// controller.navigationpropertyController
// controllerController
string searchPath = string.Join(".", uriSegments).ToLower().Split('(')[0] + "controller";
var descriptors = InitializeFullControllerDictionary().Where(t => t.Key.ToLower().Contains(searchPath)).ToList();
if (descriptors.Any())
{
var descriptor = descriptors.First().Value;
if (descriptors.Count > 1)
{
descriptor = null;
// In this mode, I think we should only ever have a single match, ready to prove me wrong?
if (descriptor == null)
throw new HttpResponseException(
request.CreateErrorResponse(HttpStatusCode.InternalServerError,
"Multiple controllers were found that match this namespace request."));
}
if (descriptor != null)
return descriptor;
}
}
return base.SelectController(request);
}
}
You can use a Custsom MapODataServiceRoute.
The below is an example from WebApiConfig.cs
The controllers are registered with the CustomMapODataServiceRoute and its a bit cumbersome having to include typeof(NameOfController) for every controller. One of my endpoints has 22 separate controllers, but thus far it's worked.
Registering Controllers - Showing two separate OData endpoints in the same project, both containing custom functions
// Continuing Education
ODataConventionModelBuilder continuingEdBuilder = new ODataConventionModelBuilder();
continuingEdBuilder.Namespace = "db_api.Models";
var continuingEdGetCourse = continuingEdBuilder.Function("GetCourse");
continuingEdGetCourse.Parameter<string>("term_code");
continuingEdGetCourse.Parameter<string>("ssts_code");
continuingEdGetCourse.Parameter<string>("ptrm_code");
continuingEdGetCourse.Parameter<string>("subj_code_prefix");
continuingEdGetCourse.Parameter<string>("crn");
continuingEdGetCourse.ReturnsCollectionFromEntitySet<ContinuingEducationCoursesDTO>("ContinuingEducationCourseDTO");
config.CustomMapODataServiceRoute(
routeName: "odata - Continuing Education",
routePrefix: "contEd",
model: continuingEdBuilder.GetEdmModel(),
controllers: new[] { typeof(ContinuingEducationController) }
);
// Active Directory OData Endpoint
ODataConventionModelBuilder adBuilder = new ODataConventionModelBuilder();
adBuilder.Namespace = "db_api.Models";
// CMS Groups
var cmsGroupFunc = adBuilder.Function("GetCMSGroups");
cmsGroupFunc.Parameter<string>("user");
cmsGroupFunc.ReturnsCollectionFromEntitySet<GenericValue>("GenericValue");
// Departments
var deptUsersFunc = adBuilder.Function("GetADDepartmentUsers");
deptUsersFunc.Parameter<string>("department");
deptUsersFunc.ReturnsCollectionFromEntitySet<ADUser>("ADUser");
var adUsersFunc = adBuilder.Function("GetADUser");
adUsersFunc.Parameter<string>("name");
adUsersFunc.ReturnsCollectionFromEntitySet<ADUser>("ADUser");
var deptFunc = adBuilder.Function("GetADDepartments");
deptFunc.ReturnsCollectionFromEntitySet<GenericValue>("GenericValue");
var instDeptFunc = adBuilder.Function("GetADInstructorDepartments");
instDeptFunc.ReturnsCollectionFromEntitySet<GenericValue>("GenericValue");
var adTitleFunc = adBuilder.Function("GetADTitles");
adTitleFunc.ReturnsCollectionFromEntitySet<GenericValue>("GenericValue");
var adOfficeFunc = adBuilder.Function("GetADOffices");
adOfficeFunc.ReturnsCollectionFromEntitySet<GenericValue>("GenericValue");
var adDistListFunc = adBuilder.Function("GetADDistributionLists");
adDistListFunc.ReturnsCollectionFromEntitySet<GenericValue>("GenericValue");
config.CustomMapODataServiceRoute(
routeName: "odata - Active Directory",
routePrefix: "ad",
model: adBuilder.GetEdmModel(),
controllers: new[] { typeof(DepartmentsController), typeof(CMSGroupsController)
});
Creating Custom Map OData Service Route
public static class HttpConfigExt
{
public static System.Web.OData.Routing.ODataRoute CustomMapODataServiceRoute(this HttpConfiguration configuration, string routeName,
string routePrefix, Microsoft.OData.Edm.IEdmModel model, IEnumerable<Type> controllers)
{
var routingConventions = ODataRoutingConventions.CreateDefault();
// Multiple Controllers with Multiple Custom Functions
routingConventions.Insert(0, new CustomAttributeRoutingConvention(routeName, configuration, controllers));
// Custom Composite Key Convention
//routingConventions.Insert(1, new CompositeKeyRoutingConvention());
return configuration.MapODataServiceRoute(routeName,
routePrefix,
model,
new System.Web.OData.Routing.DefaultODataPathHandler(),
routingConventions,
defaultHandler: System.Net.Http.HttpClientFactory.CreatePipeline( innerHandler: new System.Web.Http.Dispatcher.HttpControllerDispatcher(configuration),
handlers: new[] { new System.Web.OData.ODataNullValueMessageHandler() }));
}
}
public class CustomAttributeRoutingConvention : AttributeRoutingConvention
{
private readonly List<Type> _controllers = new List<Type> { typeof(System.Web.OData.MetadataController) };
public CustomAttributeRoutingConvention(string routeName, HttpConfiguration configuration, IEnumerable<Type> controllers)
: base(routeName, configuration)
{
_controllers.AddRange(controllers);
}
public override bool ShouldMapController(System.Web.Http.Controllers.HttpControllerDescriptor controller)
{
return _controllers.Contains(controller.ControllerType);
}
}

log4net smtpappender custom email recipients

I am able to use log4net to send logging information to an email address using the smtpappender and a Gmail account in a VB solution (Visual Studio 2010). The recipient is configured in the log4net config file, however I would like to be able to change the recipient email address dynamically.
Is it possible without having to write a custom smtpappender?
Wether the answer is yes or no, please give me an example, preferably in VB.
It's not possible, the current SmtpAppender won't allow it. But you're lucky, the SendBuffer in the SmtpAppender can be overridden, so you can easily add some behavior to it. I think your best bet is to use the LoggingEvent properties to set the recipient:
public class MySmtpAppender : SmtpAppender
{
protected override void SendBuffer(log4net.Core.LoggingEvent[] events)
{
var Recipients = events
.Where(e => e.Properties.Contains("recipient"))
.Select(e => e.Properties["recipient"])
.Distinct();
var RecipientsAsASingleLine = string.Join(";", Recipients.ToArray()); // or whatever the separator is
var PreviousTo = To;
To = RecipientsAsASingleLine;
base.SendBuffer(events);
To = PreviousTo;
}
}
You may want to change the way to select recipients, your call.
edit The tool recommended by stuartd works quite well (well, it is quite a simple class, but still):
Public Class MySmtpAppender
Inherits SmtpAppender
Protected Overrides Sub SendBuffer(events As log4net.Core.LoggingEvent())
Dim Recipients = events.Where(Function(e) e.Properties.Contains("recipient")).[Select](Function(e) e.Properties("recipient")).Distinct()
Dim RecipientsAsASingleLine = String.Join(";", Recipients.ToArray())
' or whatever the separator is
Dim PreviousTo = [To]
[To] = RecipientsAsASingleLine
MyBase.SendBuffer(events)
[To] = PreviousTo
End Sub
End Class
it is possible. see my answer in this question - copied code below
using System;
using System.IO;
using System.Web.Mail;
using log4net.Layout;
using log4net.Core;
using log4net.Appender;
namespace SampleAppendersApp.Appender
{
/// <summary>
/// Simple mail appender that sends individual messages
/// </summary>
/// <remarks>
/// This SimpleSmtpAppender sends each LoggingEvent received as a
/// separate mail message.
/// The mail subject line can be specified using a pattern layout.
/// </remarks>
public class SimpleSmtpAppender : AppenderSkeleton
{
public SimpleSmtpAppender()
{
}
public string To
{
get { return m_to; }
set { m_to = value; }
}
public string From
{
get { return m_from; }
set { m_from = value; }
}
public PatternLayout Subject
{
get { return m_subjectLayout; }
set { m_subjectLayout = value; }
}
public string SmtpHost
{
get { return m_smtpHost; }
set { m_smtpHost = value; }
}
#region Override implementation of AppenderSkeleton
override protected void Append(LoggingEvent loggingEvent)
{
try
{
StringWriter writer = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
string t = Layout.Header;
if (t != null)
{
writer.Write(t);
}
// Render the event and append the text to the buffer
RenderLoggingEvent(writer, loggingEvent);
t = Layout.Footer;
if (t != null)
{
writer.Write(t);
}
MailMessage mailMessage = new MailMessage();
mailMessage.Body = writer.ToString();
mailMessage.From = m_from;
mailMessage.To = m_to;
if (m_subjectLayout == null)
{
mailMessage.Subject = "Missing Subject Layout";
}
else
{
StringWriter subjectWriter = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
m_subjectLayout.Format(subjectWriter, loggingEvent);
mailMessage.Subject = subjectWriter.ToString();
}
if (m_smtpHost != null && m_smtpHost.Length > 0)
{
SmtpMail.SmtpServer = m_smtpHost;
}
SmtpMail.Send(mailMessage);
}
catch(Exception e)
{
ErrorHandler.Error("Error occurred while sending e-mail notification.", e);
}
}
override protected bool RequiresLayout
{
get { return true; }
}
#endregion // Override implementation of AppenderSkeleton
private string m_to;
private string m_from;
private PatternLayout m_subjectLayout;
private string m_smtpHost;
}
}
You can use log4Net.GlobalContext class.
code:
App.config
<appender name="SmtpLogAppender" type="log4net.Appender.SmtpAppender">
<to type="log4net.Util.PatternString" value="%property{SenderList}"/>
C# Code
GlobalContext.Properties["SenderList"] = "abc#xyz.com, def#xyz.com";
log4net.Config.XmlConfigurator.Configure();
It is possible, to a certain degree, to get dynamic recipient.
In the SMTP appender the replacements for To, CC, From etc is done during configuration. Not ideal (would be best if it were to calculate the values at every send) but still workable.
Re-configuring the logging is not free but is doable programatically. You can set your To field as such :
<to type="log4net.Util.PatternString" value="SomeAccountThatReceivesAll#yourCorp.com%property{MailRecipient}" />
then in your code you can set a comma separated list of recipient like this :
log4net.GlobalContext.Properties["MailRecipient"] = "SomeOtherAccount#yourCorp.com,YourCorpSupportForThisApp#yourCorp.com";
the important bit is that you force a re-configuration AFTER you set these values. The exact syntax will depend on your config strategy, we use a central config file for all the logging so in C# it would look like this :
log4net.Config.XmlConfigurator.ConfigureAndWatch("PathToYourCentralFile.xml");
and voila ! Dynamic recipient without any custom appenders !
Personally I would prefer the custom appender, less hackish as it does not require constant re-configuring if you need to change them often. But if you only need the 10 minute fix and the config for recipient does not change once started then I found this to be good enough.

Exception in Entity Framework: Error 0194: All artifacts loaded

This is a c#/asp.net project. The full error message I get is:Error 0194: All artifacts loaded into the item collection must have the same version. Multiple versions were encountered.
This project was started as a 3.5 and upgraded to 4.0. When I try to test any of the methods I get the error that I posted in the subject line. I am going to include the actual lines that it throws the exception on. If there is anything in people need to see to try to help let me know and I post it as well. Any help will be appreciated, I am having no luck with this.
/// <summary>
/// Initializes a new SFBExternalPaymentsEntities object using the connection string found in the 'SFBExternalPaymentsEntities' section of the application configuration file.
/// </summary>
public SFBExternalPaymentsEntities() : base("name=SFBExternalPaymentsEntities", "SFBExternalPaymentsEntities")
{
this.ContextOptions.LazyLoadingEnabled = false;
OnContextCreated();
}
/// <summary>
/// Initialize a new SFBExternalPaymentsEntities object.
/// </summary>
public SFBExternalPaymentsEntities(string connectionString) : base(connectionString, "SFBExternalPaymentsEntities")
{
this.ContextOptions.LazyLoadingEnabled = false;
OnContextCreated();
}
/// <summary>
/// Initialize a new SFBExternalPaymentsEntities object.
/// </summary>
public SFBExternalPaymentsEntities(EntityConnection connection) : base(connection, "SFBExternalPaymentsEntities")
{
this.ContextOptions.LazyLoadingEnabled = false;
OnContextCreated();
}
#endregion
Added a method calling the constructor.
public static CreditCardResponse AuthCapture(CreditCard newCC)
{
ACHResponse validateResponse = CreditCard.Validate(newCC);
if (validateResponse.Status == "Accepted")
{
Profile currentProfile = new Profile();
currentProfile = ProfilesGateWay.GetByID(newCC.ProfileID);
CreditCardTransaction newCCTransaction = CreateCreditCardTransaction(newCC, currentProfile);
ServiceClient client = new ServiceClient();
CreditCardTransactionResponse cctResponse = client.CreditCardAuthorizeAndCapture(newCCTransaction);
client.Close();
CreditCardResponse ccResponse = CreateCCResponse(cctResponse);
if (ccResponse.ResponseCode == 1)
{
int authAVS = ConvertAVStoInt(ccResponse.AVSResponse);
int appAVS = ConvertAVStoInt(newCC.AVLevel);
bool isAVSPass = CompareAVS(authAVS, appAVS);
if (isAVSPass == false)
{
ccResponse.ResponseCode = 0;
ccResponse.ResponseReasonCode = 99;
ccResponse.ResponseText = "Did not meet your AVS requirements";
return ccResponse;
}
else
{
int newCCID = CreateCreditCardRecord(newCC, currentProfile, cctResponse, "Captured", "Auth_Capture");
CreditCardRecord updateCC = CreditCardRecordsGateWay.GetByID(newCCID);
updateCC.CaptureOn = DateTime.Now;
CreditCardRecordsGateWay.Update(updateCC);
return ccResponse;
}
}
else
{
return ccResponse;
}
}
CreditCardResponse newCCResponse = new CreditCardResponse();
newCCResponse.ResponseCode = 0;
newCCResponse.AchResponse = validateResponse;
return newCCResponse;
}
public static CreditCardResponse PriorAuthCapture(CreditCard newCC)
{
CreditCardRecord ccRecord = CreditCardRecordsGateWay.GetByCCGateWayID(newCC.CreditCardTransactionID);
ServiceClient client = new ServiceClient();
CreditCardTransaction ccTransaction = client.CreditCardGetTransactionById(ccRecord.CCGatewayID);
CreditCardTransactionResponse cctResponse = client.CreditCardPriorAuthorizationCapture(ccTransaction);
if (cctResponse.ResponseCode == 1)
{
ccRecord.Status = "Captured";
ccRecord.CaptureOn = DateTime.Now;
}
CreditCardResponse ccResponse = CreateCCResponse(cctResponse);
return ccResponse;
}
protected static int CreateCreditCardRecord(CreditCard newCC, Profile currentProfile, CreditCardTransactionResponse cctResponse, string status, string transactionType)
{
CreditCardRecord newCCRecord = new CreditCardRecord();
newCCRecord.Address = newCC.Address;
newCCRecord.AddressVerificationLevel = newCC.AVLevel;
newCCRecord.Amount = newCC.Amount;
newCCRecord.CardCode = newCC.CardCode;
newCCRecord.CardNumber = newCC.CardNumber;
newCCRecord.CCGatewayID = cctResponse.CreditCardTransactionID;
newCCRecord.City = newCC.City;
newCCRecord.CompanyName = newCC.CompanyName;
newCCRecord.CreateBy = currentProfile.ACHCompanyName;
newCCRecord.CreateOn = DateTime.Now;
newCCRecord.Description = newCC.Description;
newCCRecord.Expiration = newCC.Expiration;
newCCRecord.FirstName = newCC.FirstName;
newCCRecord.LastName = newCC.LastName;
newCCRecord.Profile.ProfileID = currentProfile.ProfileID;
newCCRecord.State = newCC.State;
newCCRecord.Status = status;
newCCRecord.TransactionType = transactionType;
newCCRecord.Zip = newCC.Zip;
return CreditCardRecordsGateWay.Insert(newCCRecord);
}
You have just posted the constructors to your SFBExternalPaymentsEntities class, but you seem to say that you get an exception when you call a method that returns a collection of entities, which makes send with the exception message you are getting. The chances are the problem lies within the method you are calling, or in the code calling it rather than in the constructor code you have posted. Can you post some more relevent code or explain how the code you have posted relates to the exception.
I encountered the same problem when i updated one of my projects from .net 4.0 to 4.5, the reason probably was because there was another .edmx file in an other project that was referenced in the target project.
Solution to issue : Updated the other project that was referenced and contained .edmx file to .net 4.5 and then, Right-Clicked on every .edmx files in these 2 projects and then selected 'Run Custom Tool'
It worked for me, hope that it works for everyone reading this article

Transition workflows of a work item's states

I am developing a windows app that performs some common TFS tasks using the 2010 Beta 2 API (like creating new team projects, new work items, selective build, etc. ).
In the process of editing existing work items, I should be able to automatically set the 'Reason' field's values according to state change of the WI (mimic-ing Visual Studio). (eg)- When I edit a bug, when state changes from Active to Resolved, the default Reason is 'Fixed' and similarly the default Reason='Deferred' when state goes from Active to Closed. (As defined in the work item type definition xml file. ) This transition is easy to capture and implement inside a simple event handler on the form, since the initial state will be Active when the Bug is edited for the first time.
I want to know how to implement the remaining transitions like Resolved to Closed (Reason=Fixed), Resolved to Active (Reason=Test failed/Not fixed) or Closed to Active (Reason=Reactivated/Regression).
I know there is a method called WorkItem.GetNextState(current_state,action), but this doesn't help as it requires a specific action.
What I have done so far is shown below:
void cmbBugState_SelectedIndexChanged(object sender, EventArgs e)
{
//private enum bugWorkFlows{"Fixed","Deferred","Duplicate","As Designed","Cannot Reproduce","Obsolete","Test Failed","Not Fixed","Reactivated","Regression"}
string[] activeToResolvedReasons = { "Fixed", "Deferred", "Duplicate", "As Designed", "Cannot Reproduce", "Obsolete" };
string[] resolvedToActiveReasons = { "Test Failed", "Not fixed" };
string[] resolvedToClosedReasons = activeToResolvedReasons;
string[] closedToActiveReasons = { "Reactivated", "Regression" };
string[] activeToClosedReasons = activeToResolvedReasons;
cmbBugReason.Items.AddRange(activeToResolvedReasons);
// Set the default reason according to change of state of the work item.
if (cmbBugState.SelectedItem.ToString() == "Resolved")
{
cmbBugReason.Enabled = true;
cmbBugReason.SelectedItem = activeToResolvedReasons[0];
}
if (cmbBugState.SelectedItem.ToString() == "Closed")
{
cmbBugReason.Enabled = true;
cmbBugReason.SelectedItem = activeToResolvedReasons[1];
}
}
Can anyone show how to handle these events on the form?
Thanks,
Tara.
I tried GetNextState. It was never reliable enough for what I needed.
So I "rolled my own" state transition code that has worked very well for me when I am moving from State "A" to State "B". It is a bit long, but it should have what you are looking for in it.
As a side note: Because this does not use the GetNextState method it has to get the next state somehow. The way it does this is by downloading the XML of the work item type in question. It parses that out and uses that to make a Transition list (_allTransistions).
The permissions levels in TFS 2010 needed to do this are: Team Foundation Administrators or Project Administrators. (As a side note, in TFS 2008 and 2005 all valid users could do this.)
The full code that uses this can be found in the WorkItemHelpers.cs file in the TFS Aggregator project on codeplex.
public static void TransitionToState(this WorkItem workItem, string state, string commentPrefix)
{
// Set the sourceWorkItem's state so that it is clear that it has been moved.
string originalState = (string)workItem.Fields["State"].Value;
// Try to set the state of the source work item to the "Deleted/Moved" state (whatever is defined in the file).
// We need an open work item to set the state
workItem.TryOpen();
// See if we can go directly to the planned state.
workItem.Fields["State"].Value = state;
if (workItem.Fields["State"].Status != FieldStatus.Valid)
{
// Revert back to the orginal value and start searching for a way to our "MovedState"
workItem.Fields["State"].Value = workItem.Fields["State"].OriginalValue;
// If we can't then try to go from the current state to another state. Saving each time till we get to where we are going.
foreach (string curState in workItem.Type.FindNextState((string)workItem.Fields["State"].Value, state))
{
string comment;
if (curState == state)
comment = commentPrefix + Environment.NewLine + " State changed to " + state;
else
comment = commentPrefix + Environment.NewLine + " State changed to " + curState + " as part of move toward a state of " + state;
bool success = ChangeWorkItemState(workItem, originalState, curState, comment);
// If we could not do the incremental state change then we are done. We will have to go back to the orginal...
if (!success)
break;
}
}
else
{
// Just save it off if we can.
string comment = commentPrefix + "\n State changed to " + state;
ChangeWorkItemState(workItem, originalState, state, comment);
}
}
private static bool ChangeWorkItemState(this WorkItem workItem, string orginalSourceState, string destState, String comment)
{
// Try to save the new state. If that fails then we also go back to the orginal state.
try
{
workItem.TryOpen();
workItem.Fields["State"].Value = destState;
workItem.History = comment;
workItem.Save();
return true;
}
catch (Exception)
{
// Revert back to the original value.
workItem.Fields["State"].Value = orginalSourceState;
return false;
}
}
/// <summary>
/// Used to find the next state on our way to a destination state.
/// (Meaning if we are going from a "Not-Started" to a "Done" state,
/// we usually have to hit a "in progress" state first.
/// </summary>
/// <param name="wiType"></param>
/// <param name="fromState"></param>
/// <param name="toState"></param>
/// <returns></returns>
public static IEnumerable<string> FindNextState(this WorkItemType wiType, string fromState, string toState)
{
var map = new Dictionary<string, string>();
var edges = wiType.GetTransitions().ToDictionary(i => i.From, i => i.To);
var q = new Queue<string>();
map.Add(fromState, null);
q.Enqueue(fromState);
while (q.Count > 0)
{
var current = q.Dequeue();
foreach (var s in edges[current])
{
if (!map.ContainsKey(s))
{
map.Add(s, current);
if (s == toState)
{
var result = new Stack<string>();
var thisNode = s;
do
{
result.Push(thisNode);
thisNode = map[thisNode];
} while (thisNode != fromState);
while (result.Count > 0)
yield return result.Pop();
yield break;
}
q.Enqueue(s);
}
}
}
// no path exists
}
private static readonly Dictionary<WorkItemType, List<Transition>> _allTransistions = new Dictionary<WorkItemType, List<Transition>>();
/// <summary>
/// Deprecated
/// Get the transitions for this <see cref="WorkItemType"/>
/// </summary>
/// <param name="workItemType"></param>
/// <returns></returns>
public static List<Transition> GetTransitions(this WorkItemType workItemType)
{
List<Transition> currentTransistions;
// See if this WorkItemType has already had it's transistions figured out.
_allTransistions.TryGetValue(workItemType, out currentTransistions);
if (currentTransistions != null)
return currentTransistions;
// Get this worktype type as xml
XmlDocument workItemTypeXml = workItemType.Export(false);
// Create a dictionary to allow us to look up the "to" state using a "from" state.
var newTransistions = new List<Transition>();
// get the transistions node.
XmlNodeList transitionsList = workItemTypeXml.GetElementsByTagName("TRANSITIONS");
// As there is only one transistions item we can just get the first
XmlNode transitions = transitionsList[0];
// Iterate all the transitions
foreach (XmlNode transitionXML in transitions)
{
// See if we have this from state already.
string fromState = transitionXML.Attributes["from"].Value;
Transition transition = newTransistions.Find(trans => trans.From == fromState);
if (transition != null)
{
transition.To.Add(transitionXML.Attributes["to"].Value);
}
// If we could not find this state already then add it.
else
{
// save off the transistion (from first so we can look up state progression.
newTransistions.Add(new Transition
{
From = transitionXML.Attributes["from"].Value,
To = new List<string> { transitionXML.Attributes["to"].Value }
});
}
}
// Save off this transition so we don't do it again if it is needed.
_allTransistions.Add(workItemType, newTransistions);
return newTransistions;
}