Create An Event and push to MailChimp using MailChimp Net V3 - mailchimp

Here's an (php) example of creating an Event and pushing it to MailChimp.
Anybody knows how this can be accomplished using C# and MailChimp Net V3?
PHP-example:
https://mailchimp.com/developer/marketing/guides/track-outside-activity-events/#create-an-event

I've found a way, though you'll need to create a new Logic class.
public class MemberEventResponse
{
}
public class MailChimpMemberEventLogic : BaseLogic
{
public MailChimpMemberEventLogic( MailChimpOptions options )
: base( options )
{
}
public async Task<MemberEventResponse> CreateMemberEventAsync( string listId, string email, string eventName )
{
var baseUrl = $"/lists/{listId}/members/{Hash(email)}/events";
using ( var client = CreateMailClient( baseUrl ) )
{
var response = await client.PostAsJsonAsync( "", new { name = eventName } ).ConfigureAwait( false );
await response.EnsureSuccessMailChimpAsync().ConfigureAwait( false );
var listActivityResponse = await response.Content.ReadAsAsync<MemberEventResponse>().ConfigureAwait( false );
return listActivityResponse;
}
}
}
Then you just need to instantiate it, and create the event.
var logic = new MailChimpMemberEventLogic( _mailChimpOptions );
await logic.CreateMemberEventAsync( _mailChimpListId, email, eventName );
NB, I haven't looked into the response contents...

Related

Masstransit how to get ILifetimeScope in custom filter

I'm using MT version 6.3.2.
I have built a web api which then send the message to consumer queue. I have another process that consumes the message.
Send in api:
var endpoint = await Bus.GetSendEndpoint(QueueUri);
await endpoint.Send(command);
In the consumer process, I use HostBuilder to register autofac
var host = new HostBuilder()
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>((hostBuilderContext, builder) =>
{
builder.RegisterModule(module);
}
In module I register an IBus instance, I have added a filter like this:
builder.Register(cc =>
{return Bus.Factory.CreateUsingRabbitMq(rabbit =>
{
rabbit.Durable = true;
rabbit.Host(ServerName, h =>
{
h.Username(Username);
h.Password(Password);
});
rabbit.ReceiveEndpoint(Queue, rec =>
{
rec.AddPipeSpecification(new MyFilterSpecification<T>());
rec.Consumer<TConsumer>(cc);
});
});
})
.As<IBusControl>()
.As<IBus>()
.SingleInstance();
I also registered a BackgroundService to start and stop the bus.
In MyFilter, I would like to resolve a dependency registered with InstancePerLifetimeScope in the Send method of the filter.
public class MyFilter<T> : IFilter<T> where T : class, PipeContext
{
public Task Send(T context, IPipe<T> next)
{
context.TryGetPayload(out ILifetimeScope scope);
// scope here is null
}
}
As above, the scope is null. However later on in the pipeline the same code in my Consumer.Consume() returns a value. I've seen code samples where you can get ILifetimeScope in filter, I'm not sure why it's not working for me.
How can I resolve my dependency in filters? I'm not able to use constructor injection as that only works for singleton dependencies.
If you are sending outside of a consumer, using ISendEndpointProvider, you need to create a scope as shown below. The lifetime scope will be included in the payload and available to the filter.
public static async Task Main()
{
var builder = new ContainerBuilder();
builder.AddMassTransit(x =>
{
x.AddBus(provider => Bus.Factory.CreateUsingInMemory(cfg =>
{
cfg.ConfigureSend(s => s.UseFilter(new MySendFilter()));
}));
});
var container = builder.Build();
var busControl = container.Resolve<IBusControl>();
await busControl.StartAsync();
try
{
using var scope = container.BeginLifetimeScope();
var provider = scope.Resolve<ISendEndpointProvider>();
var endpint = await provider.GetSendEndpoint(new Uri("queue:some-address"));
await endpoint.Send<SomeMessage>(new { Value = "Hello"});
}
finally
{
await busControl.StopAsync();
}
}
class MySendFilter :
IFilter<SendContext>
{
public async Task Send(SendContext context, IPipe<SendContext> next)
{
var lifetimeScope = context.GetPayload<ILifetimeScope>();
await next.Send(context);
}
public void Probe(ProbeContext context)
{
}
}

How to get data stored as subject rxjs

I am working on displaying the details of event clicked. I have stored all the events inside an array.
When the user clicks on the event then its id is passed which checks inside the array and it passes the result into service.
showDetail(id){
let obj = this.events;
let newArr = Object.values(obj);
let result = newArr.filter(function(el) {
return el["id"] == id;
});
this.articleService.sendMessage(result);
let url = `/article/${id}`;
this.router.navigate([url]);
}
service
private detailSubject = new Subject<any>();
sendMessage(formData: any) {
this.detailSubject.next({formData});
}
getMessage(): Observable<any> {
return this.detailSubject.asObservable();
}
Now in my article/id page.
I am not being able to retrieve this passed array.
I have following code
ngOnInit() {
this.articleService.getMessage().subscribe(
res => {
this.loadArticleDetail(res["formData"]);
},
error => {
console.log("Error loading data");
}
);
}
this.articleService.sendMessage(result); // <-- Subject.next()
let url = `/article/${id}`;
this.router.navigate([url]); // <-- Subject.subscribe() after Subject.next(), so value already emitted
You already added BehaviorSubject tag. So use it. Also, getMessage(): Observable<any> { doesnt do anything except returns Observable. Feels redundant:
private detailSubject = new BehaviorSubject<any>(null);
message$ = this.detailSubject.asObservable();
sendMessage(formData: any) {
this.detailSubject.next({formData});
}
And
ngOnInit() {
this.articleService.message$.subscribe(...

MS Bot blob storage not able to find the current user

We are trying to persist user information in Blob Storage. We can write, verify the file exists fine, but when we try and access it in a dialog from context the user is not found.
In our initial connection endpoint, check if user exists, if not, we create one:
await storage.write({
[`directline/users/${userId}`]: {
[USER_STATE_PROPERTY]: {
customData: {},
}
}
});
Record is in blob storage. The blob storage we have is like so:
import {
AutoSaveStateMiddleware,
BotFrameworkAdapter,
UserState,
ConversationState
} from 'botbuilder';
import { log } from './logger';
import { BlobStorage } from 'botbuilder-azure';
import Bot from '../bot';
const storage = new BlobStorage({
containerName: BLOB_CONTAINER_NAME,
storageAccountOrConnectionString: BLOB_CONNECTION_STRING
});
const userState = new UserState(storage);
const conversationState = new ConversationState(storage);
const botInstance = new Bot(conversationState, userState);
const adapter = new BotFrameworkAdapter({
appId: APP_ID,
appPassword: APP_PASSWORD,
});
adapter.use(new AutoSaveStateMiddleware(conversationState, userState));
...
However, when a conversation starts we cannot find the current user:
class Bot {
private conversationState;
private userState;
private dialogState;
private dialogs;
private userProfile;
constructor(conversationState: ConversationState, userState) {
this.conversationState = conversationState;
this.userState = userState;
this.dialogState = this.conversationState.createProperty(DIALOG_STATE_PROPERTY);
this.userProfile = this.userState.createProperty(USER_STATE_PROPERTY);
this.dialogs = new DialogSet(this.dialogState);
}
public async onTurn(turnContext) {
const { type, membersAdded, recipient } = turnContext.activity;
if (type === ActivityTypes.Message || type === ActivityTypes.Event) {
const user = await this.userProfile.get(turnContext, {});
// user is always {}
}
}
So bad. Error was missing trailing / that MS appends to each id

IdentityServer4 Net Core 2 not calling custom iProfileService

I've upgraded my Identity Server project to Net Core 2 and now I am not able to get the iProfileService object to be called to add in custom user claims. It did work in Net Core 1.
Startup.cs ConfigureServices function
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddTransient<IProfileService, M25ProfileService>();
//Load certificate
var cert = new X509Certificate2(Path.Combine(_environment.ContentRootPath,
"m25id-cert.pfx"), "mypassword");
services.AddIdentityServer()
.AddSigningCredential(cert)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
//options.EnableTokenCleanup = true;
//options.TokenCleanupInterval = 30;
})
.AddProfileService<M25ProfileService>();
.AddAspNetIdentity<ApplicationUser>();
M25ProfileService.cs
public class M25ProfileService : IProfileService
{
public M25ProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = _userManager.GetUserAsync(context.Subject).Result;
var claims = new List<Claim>
{
new Claim(JwtClaimTypes.GivenName, user.FirstName),
new Claim(JwtClaimTypes.FamilyName, user.LastName),
new Claim(IdentityServerConstants.StandardScopes.Email, user.Email),
new Claim("uid", user.Id),
new Claim(JwtClaimTypes.ZoneInfo, user.TimeZone)
};
if (user.UserType != null)
claims.Add(new Claim("mut", ((int)user.UserType).ToString()));
context.IssuedClaims.AddRange(claims);
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
var user = _userManager.GetUserAsync(context.Subject).Result;
context.IsActive = user != null;
return Task.FromResult(0);
}
}
}
Config.cs
public class Config
{
// try adding claims to id token
public static IEnumerable<IdentityResource> GetIdentityResources()
{
var m25Profile = new IdentityResource(
"m25.profile",
"m25 Profile",
new[]
{
ClaimTypes.Name,
ClaimTypes.Email,
IdentityServerConstants.StandardScopes.OpenId,
JwtClaimTypes.GivenName,
JwtClaimTypes.FamilyName,
IdentityServerConstants.StandardScopes.Email,
"uid",
JwtClaimTypes.ZoneInfo
}
);
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
m25Profile
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
//Try adding claims to access token
return new List<ApiResource>
{
new ApiResource(
"m25api",
"message25 API",
new[]
{
ClaimTypes.Name,
ClaimTypes.Email,
IdentityServerConstants.StandardScopes.OpenId,
JwtClaimTypes.GivenName,
JwtClaimTypes.FamilyName,
IdentityServerConstants.StandardScopes.Email,
"uid",
JwtClaimTypes.ZoneInfo
}
)
};
}
public static IEnumerable<Client> GetClients()
{
// client credentials client
return new List<Client>
{
new Client
{
ClientId = "client",
ClientName = "Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"m25api"
}
},
// Local Development Client
new Client
{
ClientId = "m25AppDev",
ClientName = "me25",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
RedirectUris = { "http://localhost:4200/authorize.html" },
PostLogoutRedirectUris = { "http://localhost:4200/index.html" },
AllowedCorsOrigins = { "http://localhost:4200" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
JwtClaimTypes.GivenName,
"mut",
"m25api"
},
AllowOfflineAccess = true,
IdentityTokenLifetime = 300,
AccessTokenLifetime = 86400
}
};
}
}
The first thing I'm trying is just to get the identity server to allow me to login and show the user claims similar to the id4 samples. When I login, the standard claims are listed but none of the custom claims. I've put break points in the M25ProfileService class but they never get hit. It seems that ID4 is never using the customer ProfileService class but I do have it in my startup.cs.
I've also tried from my test JS Client and get the same results. Here's a snippet from my JS Client:
var config = {
authority: "http://localhost:5000",
client_id: "m25AppDev",
redirect_uri: "http://localhost:4200/authorize.html",
response_type: "id_token token",
scope:"openid profile m25api",
post_logout_redirect_uri : "http://localhost:4200/index.html"
};
var mgr = new Oidc.UserManager(config);
mgr.getUser().then(function (user) {
if (user) {
log("User logged in", user.profile);
document.getElementById("accessToken").innerHTML = "Bearer " + user.access_token + "\r\n";
}
else {
log("User not logged in");
}
});
function login() {
mgr.signinRedirect();
}
At this point, I'm not sure what to try. I thought if I added the claims to the id token (GetIdentityResources() function from what I understand) and even the access token (GetApiResources() function from what I understand), I'd see the claims but nothing seems to work. Please help! Thanks in advance!
Also, I used to be able to get the custom claims from my client as well as from the Identity Server's own index page that renders after log
Change the order of these lines of code:
.AddProfileService<M25ProfileService>()
.AddAspNetIdentity<ApplicationUser>();
One if overwriting the other.
I figured it out. Thanks to some code on GitHub, I was able to figure out what I was missing. I just needed to add these 2 lines to each client's config in config.cs and all worked perfect!
AlwaysSendClientClaims = true,
AlwaysIncludeUserClaimsInIdToken = true
This works for remote clients. However, I still can't get it to work when I'm on the ID Server itself logging in (not from a client). That's not a big deal for now but could be something in the future. If/When I figure that piece out, I'll try to remember to update my answer. Meanwhile, I hope this helps others.
In addition to the answers above (and beside the fact that the Startup.cs shown in the question already contained the relevant line of code) I'd like to add another, yet very simple cause for why the Profile Service might not be called:
Don't forget to register the service with the dependency injection container!
As having just .AddProfileService<ProfileService>() is not enough.
You would also need:
services.AddScoped<IProfileService, ProfileService>();
Or:
services.AddTransient<IProfileService, ProfileService>();

web api - asp.net identity token expires even for the subsequent request

I am using asp.net identity for the token based authentication in web api.
For refresh token, I've implemented based on the following link
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
I've added the following two classes and mentioned in the start up configuration.
From the ui I've called with username and password alone through the api
http://domain/token
When I call the above api, the request directly goes to the method ValidateClientAuthentication.
But in this method the logic is, we need to send the client id and client secret.
How do we know these two before the user login for the specific user?
I thought the work flow should be like, we need to check username and password against database and should generate the access token and refresh token.
But here where do i do this logic.
What is the work flow of this system mentioned in the sample?
Before this system, I'll call the Common/login api in my application, and after successful verification,
I'll call the code to make the user as logged in
var userIdentity=await user.GenerateUserIdentityAsync(UserManager);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, userIdentity);
After the above code, I'll generate the access token from the user identity.
I've tried many times with the following implementation and fed up with the flow.
Help me regarding the logic and the flow mentioned here.
SimpleAuthorizationServerProvider
namespace AngularJSAuthentication.API.Providers
{
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId = string.Empty;
string clientSecret = string.Empty;
Client client = null;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
if (context.ClientId == null)
{
//Remove the comments from the below line context.SetError, and invalidate context
//if you want to force sending clientId/secrects once obtain access tokens.
context.Validated();
//context.SetError("invalid_clientId", "ClientId should be sent.");
return Task.FromResult<object>(null);
}
using (AuthRepository _repo = new AuthRepository())
{
client = _repo.FindClient(context.ClientId);
}
if (client == null)
{
context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
return Task.FromResult<object>(null);
}
if (client.ApplicationType == Models.ApplicationTypes.NativeConfidential)
{
if (string.IsNullOrWhiteSpace(clientSecret))
{
context.SetError("invalid_clientId", "Client secret should be sent.");
return Task.FromResult<object>(null);
}
else
{
if (client.Secret != Helper.GetHash(clientSecret))
{
context.SetError("invalid_clientId", "Client secret is invalid.");
return Task.FromResult<object>(null);
}
}
}
if (!client.Active)
{
context.SetError("invalid_clientId", "Client is inactive.");
return Task.FromResult<object>(null);
}
context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin);
context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
if (allowedOrigin == null) allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = await _repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
identity.AddClaim(new Claim("sub", context.UserName));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
},
{
"userName", context.UserName
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
var currentClient = context.ClientId;
if (originalClient != currentClient)
{
context.SetError("invalid_clientId", "Refresh token is issued to a different clientId.");
return Task.FromResult<object>(null);
}
// Change auth ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
var newClaim = newIdentity.Claims.Where(c => c.Type == "newClaim").FirstOrDefault();
if (newClaim != null)
{
newIdentity.RemoveClaim(newClaim);
}
newIdentity.AddClaim(new Claim("newClaim", "newValue"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
}
}
SimpleRefreshTokenProvider
namespace AngularJSAuthentication.API.Providers
{
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var clientid = context.Ticket.Properties.Dictionary["as:client_id"];
if (string.IsNullOrEmpty(clientid))
{
return;
}
var refreshTokenId = Guid.NewGuid().ToString("n");
using (AuthRepository _repo = new AuthRepository())
{
var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
var token = new RefreshToken()
{
Id = Helper.GetHash(refreshTokenId),
ClientId = clientid,
Subject = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
};
context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await _repo.AddRefreshToken(token);
if (result)
{
context.SetToken(refreshTokenId);
}
}
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
string hashedTokenId = Helper.GetHash(context.Token);
using (AuthRepository _repo = new AuthRepository())
{
var refreshToken = await _repo.FindRefreshToken(hashedTokenId);
if (refreshToken != null )
{
//Get protectedTicket from refreshToken class
context.DeserializeTicket(refreshToken.ProtectedTicket);
var result = await _repo.RemoveRefreshToken(hashedTokenId);
}
}
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
}
How about using refresh tokens and storing them in a database, like in these two examples:
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
http://leastprivilege.com/2013/11/15/adding-refresh-tokens-to-a-web-api-v2-authorization-server/
As broadly described in the first link, you can create your own token provider implementation to handle token refresh:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var clientid = context.Ticket.Properties.Dictionary["as:client_id"];
if (string.IsNullOrEmpty(clientid))
{
return;
}
var refreshTokenId = Guid.NewGuid().ToString("n");
using (AuthRepository _repo = new AuthRepository())
{
var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
var token = new RefreshToken()
{
Id = Helper.GetHash(refreshTokenId),
ClientId = clientid,
Subject = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
};
context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await _repo.AddRefreshToken(token);
if (result)
{
context.SetToken(refreshTokenId);
}
}
}
}

Resources