I've been looking into consuming multiple RSS feeds with Spring Integration. I followed the integration guide here: https://spring.io/guides/gs/integration/ which is great.
Is it possible to have multiple inbound channel adaptors (for multiple feeds) writing into one channel?
Also is it possible to use the channel adaptor ID within the data to identify the feed (e.g. Spring Blog)?
Any sample code would be fantastic.
Yes, just give the channel adapter an id and name the channel (if there is no channel, the id becomes the adapter's channel name). You can feed multiple adapters to the same channel. Add a header enricher to identify the source...
<feed:inbound-channel-adapter id="spring"
channel="springblog" url="http://spring.io/blog.atom" auto-startup="${auto.startup:true}">
<int:poller fixed-rate="5000"/>
</feed:inbound-channel-adapter>
<int:header-enricher input-channel="springblog" output-channel="news">
<int:header name="source" value="spring.blog"/>
</int:header-enricher>
<int:transformer
input-channel="news"
expression=
"headers['source'] + ':' + payload.title + ' # ' + payload.link + '#{systemProperties['line.separator']}'"
output-channel="file"/>
<file:outbound-channel-adapter id="file"
mode="APPEND"
charset="UTF-8"
directory="/tmp/si"
filename-generator-expression="'${feed.file.name:SpringBlog}'"/>
Using the newer Java DSL instead of XML, this would be...
#Bean
public IntegrationFlow blog() throws Exception {
return IntegrationFlows
.from(new FeedEntryMessageSource(new URL(BLOG_URI), "blog"), e -> e.id("blog").poller(Pollers.fixedDelay(5000)))
.enrichHeaders(h -> h.header("source", "spring.blog"))
.channel("news")
.get();
}
#Bean
public IntegrationFlow newsFlow() {
return IntegrationFlows.from("news")
.transform("headers['source'] + ':' + payload.title + ' # ' + payload.link + '" + newline + "'") // SpEL
.handle(Files.outboundAdapter(new File("/tmp/si/"))
.fileNameExpression("'SpringBlogDSL'")
.fileExistsMode(FileExistsMode.APPEND))
.get();
}
EDIT
Dynamic flow registration...
#Autowired
private IntegrationFlowContext flowContext;
...
IntegrationFlow newFLow = IntegrationFlows
.from(new FeedEntryMessageSource(new URL(BLOG_URI), "blog"), e -> e.id("blog").poller(Pollers.fixedDelay(5000)))
.enrichHeaders(h -> h.header("source", "spring.blog"))
.channel("news")
.get();
this.flowContent.registration(newFlow).register();
See the dynamic-tcp-client sample for a complete example.
Yes, you can connect several channel adapters to the same message channel. Actually any producer can send to any channel and having your request about additional info, I'd suggest to have after each feed Inbound Adapter a header enricher to populate a desired characteristic. And after that you can send to that single channel for processing.
Related
Currently, I have dozens of .NET services hosted on various machines that show up as Resources on my AppInsights Application Map, which also shows their dependencies with respect to each other, based on the HTTP requests they make.
However, the relationships between services that communicate through NServiceBus (RabbitMQ) are not shown. Now, I am able to show the messages that are either sent or handled by a service via calls to TelemetryClient.TrackXXX(), but not connect Resources on the map using this information.
I have even gone so far as to attach the parent operation ID from the NSB message sender to the message itself, and assign it to the telemetry object in the receiver, but there is still no line drawn between the services in the Application Map.
To reiterate, this is what I'm getting in the Application Map:
(NSB Message Sender) --> (Message sent/handled)
And this is what I want:
(NSB Sender) --> (Receiver)
The services in question are .NET Core 3.1.
I cannot provide the code, as this is for my work, but any help would be greatly appreciated. I've searched everywhere, and even sources that seemed like they would help, didn't.
(not signed in, posting from work)
Alright, I finally got it. My approach to correlate AppInsights resources using their NSB communication is to mimic HTTP telemetry correlation.
Below is an extension method I wrote for AppInsights' TelemetryClient. I made a subclass named RbmqMessage:NServiceBus.IMessage, given my applications use RBMQ, and gave it the following properties for the sake of correlation (all set in the service that sends the message) :
parentId: equal to DependencyTelemetry.Id
opId: value is the same in the sender's DependencyTelemetry and the receiver's RequestTelemetry. Equal to telemetry.context.operation.id
startTime: DateTime.Now was good enough for my purposes
The code in the service that sends the NSB message:
public static RbmqMessage TrackRbmq(this TelemetryClient client, RbmqMessage message)
{
var msg = message;
// I ran into some issues with Reflection
var classNameIdx = message.ToString().LastIndexOf('.') + 1;
var messageClassName = message.ToString().Substring(classNameIdx);
var telemetry = new DependencyTelemetry
{
Type = "RabbitMQ",
Data = "SEND "+messageClassName,
Name = "SEND "+messageClassName,
Timestamp = DateTime.Now,
Target = "RECEIVE "+messageClassName //matches name in the service receiving this message
};
client.TrackDependency(telemetry);
msg.parentId = telemetry.Id;
msg.opId = telemetry.Context.Operation.Id; //this wont have a value until TrackDependency is called
msg.startTime = telemetry.Timestamp;
return msg;
}
The code where you send the NSB message:
var msg = new MyMessage(); //make your existing messages inherit RbmqMessage
var correlatedMessage = _telemetryClient.TrackRbmq(msg);
MessageSession.Publish(correlatedMessage); //or however the NSB message goes out in your application
The extension method in the NServiceBus message-receiving service:
public static void TrackRbmq(this TelemetryClient client, RbmqMessage message)
{
var classnameIdx = message.ToString().LastIndexOf('.')+1;
var telemetry = new RequestTelemetry
{
Timestamp = DateTime.Now,
Name = "RECEIVE "+message.ToString().Substring(classNameIdx)
};
telemetry.Context.Operation.ParentId = message.parentId;
telemetry.Context.Operation.Id = message.opId;
telemetry.Duration = message.startTime - telemetry.Timestamp;
client.TrackRequest(telemetry);
}
And finally, just track and send the message:
var msg = new MyMessage();
_telemetryClient.TrackRbmq(msg);
MessagePipeline.Send(msg); //or however its sent in your app
I hope this saves someone the trouble I went through.
I have successfully integrated CAS for our different clients. But this time 'samlValidate' response is not consistently supplying the required attribute. Login is failing randomly because of the missing attribute in the ticket validation response. Sometimes when I clear browser history, it's receiving the attribute in the response.
Expected response:
<cas:serviceResponse xmlns:cas='http://www.xxxxx.xxx/tp/cas'>
<cas:authenticationSuccess>
<cas:user>xxxxx</cas:user>
<cas:attributes>
<cas:userNumber>1234567</cas:userNumber>
</cas:attributes>
</cas:authenticationSuccess>
</cas:serviceResponse>
Response receiving randomly:
<cas:serviceResponse xmlns:cas='http://www.xxx.xxx/tp/cas'>
<cas:authenticationSuccess>
<cas:user>xxxxxx</cas:user>
</cas:authenticationSuccess>
</cas:serviceResponse>
Please note: We have created a custom code to integrate CAS with our Asp.Net webforms application.
string userId = string.Empty;
// Look for the "ticket=" after the "?" in the URL
string tkt = HttpContext.Current.Request.QueryString["ticket"];
// Service url is the url of the Researcher Portal
string service ="www.xyz.com";
string CASHOST="https://cas.xyz.ca:8443/cas"
// First time through there is no ticket=, so redirect to CAS login
if (tkt == null || tkt.Length == 0)
{
string redir = CASHOST + "login?" +
"service=" + service;
HttpContext.Current.Response.Redirect(redir);
}
// Second time (back from CAS) there is a ticket= to validate
string validateurl = CASHOST + "serviceValidate?" +
"ticket=" + tkt +
"&service=" + service;
StreamReader Reader = new StreamReader(new WebClient().OpenRead(validateurl));
string resp = Reader.ReadToEnd();
if (isDebuggingMode)
sbDebugString.Append("****Response **** \n " + resp);
// Some boilerplate to set up the parse.
NameTable nt = new NameTable();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nt);
XmlParserContext context = new XmlParserContext(null, nsmgr, null, XmlSpace.None);
XmlTextReader reader = new XmlTextReader(resp, XmlNodeType.Element, context);
string userNumber = null;
// A very dumb use of XML. Just scan for the "userNumber". If it isn't there, it will return an empty string.
while (reader.Read())
{
if (reader.IsStartElement())
{
string tag = reader.LocalName;
if (isDebuggingMode)
sbDebugString.Append("tag : " + tag + "\n");
if (tag == "userNumber")
{
userNumber = reader.ReadString();
if (isDebuggingMode)
sbDebugString.Append("userNumber : " + userNumber + "\n");
}
}
}
Where "userNumber" attribute is not receiving always so that login fails randomly.
Please share your thoughts to resolve this issue.
Thank you in advance.
If your client application is not receiving attributes, you will need to make sure:
The client is using a version of CAS protocol that is able to
release attributes.
The client, predicated on #1, is hitting the appropriate endpoint for service ticket validation (i.e. /p3/serviceValidate).
The CAS server itself is resolving and retrieving attributes correctly.
The CAS server is authorized to release attributes to that particular client application inside its service registry.
Starting with CAS Protocol 3:
Among all features, the most noticeable update between versions 2.0 and 3.0 is the ability to return the authentication/user attributes through the new /p3/serviceValidate endpoint.
You may also find this post useful:
https://apereo.github.io/2017/06/23/cas-protocol-compatibility/
I just started using Microsoft Exchange Web Services for the first time. Want I want to be able to do is the following:
Create Meeting
Update Meeting
Cancel/Delete Meeting
These meetings are created in an ASP.NET MVC application and saved into a SQL Server database. I simply wish to integrate this with the on site Exchange Server. So far, I'm able to created my meeting with the following code:
public static Task<string> CreateMeetingAsync(string from, List<string> to, string subject, string body, string location, DateTime begin, DateTime end)
{
var tcs = new TaskCompletionSource<string>();
try
{
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013);
service.Credentials = CredentialCache.DefaultNetworkCredentials;
//service.UseDefaultCredentials = true;
// I suspect the Service URL needs to be set from the user email address because this is then used to set the organiser
// of the appointment constructed below. The Organizer is a read-only field that cannot be manually set. (security measure)
service.AutodiscoverUrl(from);
//service.Url = new Uri(WebConfigurationManager.AppSettings["ExchangeServer"]);
Appointment meeting = new Appointment(service);
meeting.Subject = subject;
meeting.Body = "<span style=\"font-family:'Century Gothic'\" >" + body + "</span><br/><br/><br/>";
meeting.Body.BodyType = BodyType.HTML;
meeting.Start = begin;
meeting.End = end;
meeting.Location = location;
meeting.ReminderMinutesBeforeStart = 60;
foreach (string attendee in to)
{
meeting.RequiredAttendees.Add(attendee);
}
meeting.Save(SendInvitationsMode.SendToAllAndSaveCopy);
tcs.TrySetResult(meeting.Id.UniqueId);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
return tcs.Task;
}
This successfully creates my meeting, places it into the user's calendar in outlook and sends a meeting request to all attendees. I noticed the following exception when attempting to call meeting.Save(SendInvitationsMode.SendToAllAndSaveCopy); twice:
This operation can't be performed because this service object already has an ID. To update this service object, use the Update() method instead.
I thought: Great, it saves the item in exchange with a unique id. I'll save this ID in my application's database and use it later to edit/cancel the meeting. That is why I return the id: tcs.TrySetResult(meeting.Id.UniqueId);
This is saved nicely into my application's database:
Now, I am attempting to do the next part where I update the meeting, but I cannot find a way to search for the item based on the unique identifier that I'm saving. An example I found on code.msdn uses the service.FindItems() method with a query that searches the subject:
string querystring = "Subject:Lunch";
FindItemsResults<Item> results = service.FindItems(WellKnownFolderName.Calendar, querystring, view);
I don't like this. There could be a chance that the user created a meeting outside of my application that coincidentally has the same subject, and here come's my application and cancel's it. I tried to determine wether it's possible to use the unique id in the query string, but this does not seem possible.
I did notice on the above query string page that the last property you can search on is (property is not specified) that searches in "all word phase properties.". I tried thus simply putting the id into the query, but this returns no results:
FindItemsResults<Item> results = service.FindItems(WellKnownFolderName.Calendar, "AAMkADJhZDQzZWFmLWYxYTktNGI1Yi1iZTA5LWVmOTE3MmJiMGIxZgBGAAAAAAAqhOzrXRdvRoA6yPu9S/XnBwDXvBDBMebkTqUWix3HxZrnAAAA2LKLAAB5iS34oLxkSJIUht/+h9J1AAFlEVLAAAA=", view);
Use the Appointment.Bind static function, providing a service object and the ItemId saved in your database. Be aware with meeting workflow (invite, accept, reject) can re-create a meeting on the same calendar with a new ItemId. But if you are just looking at the meeting you make on your own calendar, you should be OK.
I send push notifications using PushSharp.
I am facing a problem while receiving the responses in the delegate methods as mentioned in Redth documentation on GitHub.
So, of the say 10 GCM Registration Ids that I send a particular message, I receive 9 responses only. I require this kind of response to make sure that in case of canonical Ids returned I should be able to update my database accordingly.
Is this behavior by design (i.e to not wait for all the responses from GCM) which results in few responses getting dropped ?
Below is my code snippets for better understanding
//Create our push services broker
//Create our push services broker
var broker = new PushBroker();
broker.OnNotificationSent += new NotificationSentDelegate(NotificationSent);
broker.OnNotificationFailed += new PushSharp.Core.NotificationFailedDelegate(NotificationFailed);
broker.OnChannelException += new ChannelExceptionDelegate(ChannelException);
broker.OnServiceException += new ServiceExceptionDelegate(ServiceException);
broker.OnDeviceSubscriptionChanged += new DeviceSubscriptionChangedDelegate(DeviceSubscriptionChanged);
broker.OnDeviceSubscriptionExpired += new PushSharp.Core.DeviceSubscriptionExpiredDelegate(DeviceSubscriptionExpired);
broker.OnChannelCreated += new ChannelCreatedDelegate(ChannelCreated);
broker.OnChannelDestroyed += new ChannelDestroyedDelegate(ChannelDestroyed);
//---------------------------
// ANDROID GCM NOTIFICATIONS
//---------------------------
broker.RegisterGcmService(new GcmPushChannelSettings(senderID, senderAuthToken, applicationIdPackageName));
GcmNotification gcm = new GcmNotification();
gcm.RegistrationIds.Add(deviceRegistrationId1);
gcm.RegistrationIds.Add(deviceRegistrationId2);
//and so on
gcm.WithJson("{\"Test\":\"" + notificationMsg + "\",\"badge\":7,\"sound\":\"sound.caf\"}");
//Stop and wait for the queues to drains
broker.StopAllServices(true);
//<
Please advise if I am missing something in this implementation.
Thanks in advance
You're not registering to see the failed notifications as you associate the delegate with the PushSharp delegate and not your own:
broker.OnNotificationFailed += new PushSharp.Core.NotificationFailedDelegate(NotificationFailed);
should match your:
broker.OnNotificationSent += new NotificationSentDelegate(NotificationSent);
I want to get the geographic location of my website visitors and store it in my database, have fetch the ip address already.
I'm using asp.net 3.0
protected void Page_Load( object sender, EventArgs e)
{
// Track Visitors
string ipAddress = IpAddress();
string hostName = Dns .GetHostByAddres(ipAddress).HostName;
StreamWriter wrtr = new StreamWriter (Server.MapPath( "visitors.log" ), true );
wrtr.WriteLine( DateTime .Now.ToString() + " | " + ipAddress + " | " + hostName + " | " + Request.Url.ToString());
wrtr.Close();
}
private string IpAddress()
{
string strIpAddress;
strIpAddress = Request.ServerVariables[ "HTTP_X_FORWARDED_FOR" ];
if (strIpAddress == null )
strIpAddress = Request.ServerVariables[ "REMOTE_ADDR" ];
return strIpAddress;
}
There is no inbuilt method to that. There are web-services on the internet that will provide such mapping. Such databases "GeoIP databases" are also available for local use.
But it would be best to subscribe to an updated web-service and use that to get the location.
Google's Client Location API's should help you here.
Some modern browsers will send you the users location. You can use that. But since this is not followed by all browsers, it is not reliable. You can check out the Geo Location API provided by w3.org.