How to configure Filter with specific message type? - masstransit

I want to consume messages only with specific type and properties set. A sort of message content filter before any consumer instance created.
I'm trying to create a filter for specific ConsumeContext:
public class OrderFilter : IFilter<ConsumeContext<CreateOrderMessage>>
{
public Task Send(ConsumeContext<CreateOrderMessage> context, IPipe<ConsumeContext<CreateOrderMessage>> next)
{
if (context.Message.IsTrustedUser)
{
return next.Send(context); // continue processing
}
return Task.CompletedTask; // stop message processing
}
public void Probe(ProbeContext context) { }
}
How can I register such a filter?
I've tried to register it in the endpoint but with no luck. I have
cfg.ReceiveEndpoint("OrderQueue", ep =>
{
ep.UseFilter(new OrderFilter());
ep.Consumer<CreateOrderConsumer>();
});
I have the following error: Cannot convert instance argument type '{MassTransit.IReceiveEndpointConfigurator,MassTransit.RabbitMqTransport.IRabbitMqReceiveEndpointConfigurator}' to 'GreenPipes.IPipeConfigurator<MassTransit.ConsumeContext<Core.CreateOrderMessage>>'

So, there used to be an extension method for this purpose, but I can't find it. You can add the filter prior to the consumer being created by creating a filter specification and adding it as shown below.
var filter = new OrderFilter();
var specification = new FilterPipeSpecification<ConsumeContext< CreateOrderMessage >>(filter);
ep.AddPipeSpecification(specification);
If you want to execute the filter after the consumer has been created (for instance, if you're using container scope to share information), you can use a scope consume filter (which is described in several answers, as well as the documentation) or you can add your filter during consumer configuration.
ep.Consumer<CreateOrderConsumer>(cc =>
{
cc.Message<CreateOrderMessage>(mc => mc.UseFilter(new OrderFilter()));
}

Related

How to update a message inside Filter (MassTransit)

I created a filter where all messages are being captured.
I am trying to modify a messege using GetOrAddPayload() avaiable in PipeContext intereface, but unfortunately I do not understand how to implement it.
public async Task Send(ConsumeContext<TMessage> context, IPipe<ConsumeContext<TMessage>> next)
{
context.GetOrAddPayload()
await next.Send(context);
}
All messages are type of IMyContract interface that has just one property.
public interface IMyContract
{
object Message { get; }
}
How to implement PayloadFactory for above inteface to make it work?

MassTransit failing to consume

I'm switching some code which uses MassTransit (v7.2.2 on .NET 5) to use a more declarative format (and away from multiple calls to ReceiveEndpoint()) and ideally to using ConsumerDefinitions for the configuration (though not part of this example for simplicity), along with some Dependency Injection with Quartz.NET (yanked from this example, though it running 3.3.3), in doing so I find now that my Consumers are not consuming, despite messages being sent and examples referenced. Take the following standing up of the MassTransit service:
var hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddMassTransit(mt =>
{
mt.AddBus(provider => Bus.Factory.CreateUsingInMemory(cfg =>
{
//cfg.AutoStart = true; //No change when on
cfg.UseInMemoryOutbox();
cfg.ConfigureEndpoints(provider);
}));
mt.AddConsumer<TheMessageConsumer>();
services.AddMediator(cfg =>
{
cfg.AddConsumer<TheMessageConsumer>();
});
});
services.AddMassTransitHostedService();
});
var host = hostBuilder.Build();
var busControl = host.Services.GetService<IBusControl>();
busControl.Start(); //Just in case
var message = new TheMessage() { Message = $"<Message-{DateTime.Now.ToLongTimeString()}>" };
Console.WriteLine($"Sending: {message.Message}");
await busControl.Publish(message);
host.Run();
For note, the breaking out of the message sending here is to simplify my repro, as in my full code base, it's being sent by a Quartz fired job.
For this example, the message & receiver are also quite simple:
public class TheMessage
{
public string Message { get; set; }
}
public class TheMessageConsumer : IConsumer<TheMessage>
{
public Task Consume(ConsumeContext<TheMessage> context)
{
Console.WriteLine($"Message received: {context.Message.Message}");
return Task.CompletedTask;
}
}
The bus is started, either in the case I explicitly start it, the AutoStart flag is set, or the MassTransitHostedService does it, yet the message doesn't get received. Ditto if I have the full example with Quartz firing off a job with messages much later.
Can someone suggest what I am missing?
The code you posted is seriously a hodgepodge of snippets, none of which make any sense when used together. For example, AddBus is deprecated, and mediator has no business being in that project at all.
I'd suggest using one of the MassTransit Templates to create a new project from scratch (you may need to up the NuGet versions to 7.2.2).
Watch this video which explains the templates and how to use them.
With your comment, and updated question, you really only need the following:
var hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddMassTransit(mt =>
{
mt.AddConsumer<TheMessageConsumer>();
mt.UsingInMemory((context,cfg) =>
{
cfg.UseInMemoryOutbox();
cfg.ConfigureEndpoints(context);
}));
});
services.AddMassTransitHostedService();
});
var host = hostBuilder.Build();
await host.RunAsync();
If you want to test it, and send messages in the same process, simply add a BackgroundService (after AddMassTransitHostedService) to publish your messages.
You shouldn't publish until after the bus has been started.

Getting started with swashbuckle for blank web.api in vs2015

This is a beginner question. In 2015 I created a blank web.api project. I then added Swashbuckle 5.5.3 nuget package. I created a common controller listed below.
namespace StarterApi.Controllers
{
public class CommonController : ApiController
{
[HttpGet]
public HttpResponseMessage GetTestList(int id)
{
HttpResponseMessage msg = null;
try
{
//var principal = User as ClaimsPrincipal;
List<DDLDispValueVM> ctrList = new List<DDLDispValueVM> {
new DDLDispValueVM { Disp="one", Value="1"},
new DDLDispValueVM { Disp="two", Value="2"}
};
msg = Request.CreateResponse(HttpStatusCode.OK, ctrList);
}
catch (Exception ex)
{
msg = Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
}
return msg;
}
}
}
When I go to the url I get the swagger screen, but it is missing the Controller.
See Image.
I have a GitHub of the project at...
TestSwashbuckle
Here is the SwaggerConfig file
using System.Web.Http;
using WebActivatorEx;
using StarterApi;
using Swashbuckle.Application;
[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]
namespace StarterApi
{
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration.EnableSwagger(c =>
{
// By default, the service root url is inferred from the request used to access the docs.
// However, there may be situations (e.g. proxy and load-balanced environments) where this does not
// resolve correctly. You can workaround this by providing your own code to determine the root URL.
//
//c.RootUrl(req => GetRootUrlFromAppConfig());
// If schemes are not explicitly provided in a Swagger 2.0 document, then the scheme used to access
// the docs is taken as the default. If your API supports multiple schemes and you want to be explicit
// about them, you can use the "Schemes" option as shown below.
//
//c.Schemes(new[] { "http", "https" });
// Use "SingleApiVersion" to describe a single version API. Swagger 2.0 includes an "Info" object to
// hold additional metadata for an API. Version and title are required but you can also provide
// additional fields by chaining methods off SingleApiVersion.
//
c.SingleApiVersion("v1", "StarterApi");
// If your API has multiple versions, use "MultipleApiVersions" instead of "SingleApiVersion".
// In this case, you must provide a lambda that tells Swashbuckle which actions should be
// included in the docs for a given API version. Like "SingleApiVersion", each call to "Version"
// returns an "Info" builder so you can provide additional metadata per API version.
//
//c.MultipleApiVersions(
// (apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion),
// (vc) =>
// {
// vc.Version("v2", "Swashbuckle Dummy API V2");
// vc.Version("v1", "Swashbuckle Dummy API V1");
// });
// You can use "BasicAuth", "ApiKey" or "OAuth2" options to describe security schemes for the API.
// See https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md for more details.
// NOTE: These only define the schemes and need to be coupled with a corresponding "security" property
// at the document or operation level to indicate which schemes are required for an operation. To do this,
// you'll need to implement a custom IDocumentFilter and/or IOperationFilter to set these properties
// according to your specific authorization implementation
//
//c.BasicAuth("basic")
// .Description("Basic HTTP Authentication");
//
// NOTE: You must also configure 'EnableApiKeySupport' below in the SwaggerUI section
//c.ApiKey("apiKey")
// .Description("API Key Authentication")
// .Name("apiKey")
// .In("header");
//
//c.OAuth2("oauth2")
// .Description("OAuth2 Implicit Grant")
// .Flow("implicit")
// .AuthorizationUrl("http://petstore.swagger.wordnik.com/api/oauth/dialog")
// //.TokenUrl("https://tempuri.org/token")
// .Scopes(scopes =>
// {
// scopes.Add("read", "Read access to protected resources");
// scopes.Add("write", "Write access to protected resources");
// });
// Set this flag to omit descriptions for any actions decorated with the Obsolete attribute
//c.IgnoreObsoleteActions();
// Each operation be assigned one or more tags which are then used by consumers for various reasons.
// For example, the swagger-ui groups operations according to the first tag of each operation.
// By default, this will be controller name but you can use the "GroupActionsBy" option to
// override with any value.
//
//c.GroupActionsBy(apiDesc => apiDesc.HttpMethod.ToString());
// You can also specify a custom sort order for groups (as defined by "GroupActionsBy") to dictate
// the order in which operations are listed. For example, if the default grouping is in place
// (controller name) and you specify a descending alphabetic sort order, then actions from a
// ProductsController will be listed before those from a CustomersController. This is typically
// used to customize the order of groupings in the swagger-ui.
//
//c.OrderActionGroupsBy(new DescendingAlphabeticComparer());
// If you annotate Controllers and API Types with
// Xml comments (http://msdn.microsoft.com/en-us/library/b2s063f7(v=vs.110).aspx), you can incorporate
// those comments into the generated docs and UI. You can enable this by providing the path to one or
// more Xml comment files.
//
//c.IncludeXmlComments(GetXmlCommentsPath());
// Swashbuckle makes a best attempt at generating Swagger compliant JSON schemas for the various types
// exposed in your API. However, there may be occasions when more control of the output is needed.
// This is supported through the "MapType" and "SchemaFilter" options:
//
// Use the "MapType" option to override the Schema generation for a specific type.
// It should be noted that the resulting Schema will be placed "inline" for any applicable Operations.
// While Swagger 2.0 supports inline definitions for "all" Schema types, the swagger-ui tool does not.
// It expects "complex" Schemas to be defined separately and referenced. For this reason, you should only
// use the "MapType" option when the resulting Schema is a primitive or array type. If you need to alter a
// complex Schema, use a Schema filter.
//
//c.MapType<ProductType>(() => new Schema { type = "integer", format = "int32" });
// If you want to post-modify "complex" Schemas once they've been generated, across the board or for a
// specific type, you can wire up one or more Schema filters.
//
//c.SchemaFilter<ApplySchemaVendorExtensions>();
// In a Swagger 2.0 document, complex types are typically declared globally and referenced by unique
// Schema Id. By default, Swashbuckle does NOT use the full type name in Schema Ids. In most cases, this
// works well because it prevents the "implementation detail" of type namespaces from leaking into your
// Swagger docs and UI. However, if you have multiple types in your API with the same class name, you'll
// need to opt out of this behavior to avoid Schema Id conflicts.
//
//c.UseFullTypeNameInSchemaIds();
// Alternatively, you can provide your own custom strategy for inferring SchemaId's for
// describing "complex" types in your API.
//
//c.SchemaId(t => t.FullName.Contains('`') ? t.FullName.Substring(0, t.FullName.IndexOf('`')) : t.FullName);
// Set this flag to omit schema property descriptions for any type properties decorated with the
// Obsolete attribute
//c.IgnoreObsoleteProperties();
// In accordance with the built in JsonSerializer, Swashbuckle will, by default, describe enums as integers.
// You can change the serializer behavior by configuring the StringToEnumConverter globally or for a given
// enum type. Swashbuckle will honor this change out-of-the-box. However, if you use a different
// approach to serialize enums as strings, you can also force Swashbuckle to describe them as strings.
//
//c.DescribeAllEnumsAsStrings();
// Similar to Schema filters, Swashbuckle also supports Operation and Document filters:
//
// Post-modify Operation descriptions once they've been generated by wiring up one or more
// Operation filters.
//
//c.OperationFilter<AddDefaultResponse>();
//
// If you've defined an OAuth2 flow as described above, you could use a custom filter
// to inspect some attribute on each action and infer which (if any) OAuth2 scopes are required
// to execute the operation
//
//c.OperationFilter<AssignOAuth2SecurityRequirements>();
// Post-modify the entire Swagger document by wiring up one or more Document filters.
// This gives full control to modify the final SwaggerDocument. You should have a good understanding of
// the Swagger 2.0 spec. - https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md
// before using this option.
//
//c.DocumentFilter<ApplyDocumentVendorExtensions>();
// In contrast to WebApi, Swagger 2.0 does not include the query string component when mapping a URL
// to an action. As a result, Swashbuckle will raise an exception if it encounters multiple actions
// with the same path (sans query string) and HTTP method. You can workaround this by providing a
// custom strategy to pick a winner or merge the descriptions for the purposes of the Swagger docs
//
//c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
// Wrap the default SwaggerGenerator with additional behavior (e.g. caching) or provide an
// alternative implementation for ISwaggerProvider with the CustomProvider option.
//
//c.CustomProvider((defaultProvider) => new CachingSwaggerProvider(defaultProvider));
})
.EnableSwaggerUi(c =>
{
// Use the "InjectStylesheet" option to enrich the UI with one or more additional CSS stylesheets.
// The file must be included in your project as an "Embedded Resource", and then the resource's
// "Logical Name" is passed to the method as shown below.
//
//c.InjectStylesheet(containingAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testStyles1.css");
// Use the "InjectJavaScript" option to invoke one or more custom JavaScripts after the swagger-ui
// has loaded. The file must be included in your project as an "Embedded Resource", and then the resource's
// "Logical Name" is passed to the method as shown above.
//
//c.InjectJavaScript(thisAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testScript1.js");
// The swagger-ui renders boolean data types as a dropdown. By default, it provides "true" and "false"
// strings as the possible choices. You can use this option to change these to something else,
// for example 0 and 1.
//
//c.BooleanValues(new[] { "0", "1" });
// By default, swagger-ui will validate specs against swagger.io's online validator and display the result
// in a badge at the bottom of the page. Use these options to set a different validator URL or to disable the
// feature entirely.
//c.SetValidatorUrl("http://localhost/validator");
//c.DisableValidator();
// Use this option to control how the Operation listing is displayed.
// It can be set to "None" (default), "List" (shows operations for each resource),
// or "Full" (fully expanded: shows operations and their details).
//
//c.DocExpansion(DocExpansion.List);
// Specify which HTTP operations will have the 'Try it out!' option. An empty paramter list disables
// it for all operations.
//
//c.SupportedSubmitMethods("GET", "HEAD");
// Use the CustomAsset option to provide your own version of assets used in the swagger-ui.
// It's typically used to instruct Swashbuckle to return your version instead of the default
// when a request is made for "index.html". As with all custom content, the file must be included
// in your project as an "Embedded Resource", and then the resource's "Logical Name" is passed to
// the method as shown below.
//
//c.CustomAsset("index", containingAssembly, "YourWebApiProject.SwaggerExtensions.index.html");
// If your API has multiple versions and you've applied the MultipleApiVersions setting
// as described above, you can also enable a select box in the swagger-ui, that displays
// a discovery URL for each version. This provides a convenient way for users to browse documentation
// for different API versions.
//
//c.EnableDiscoveryUrlSelector();
// If your API supports the OAuth2 Implicit flow, and you've described it correctly, according to
// the Swagger 2.0 specification, you can enable UI support as shown below.
//
//c.EnableOAuth2Support(
// clientId: "test-client-id",
// clientSecret: null,
// realm: "test-realm",
// appName: "Swagger UI"
// //additionalQueryStringParams: new Dictionary<string, string>() { { "foo", "bar" } }
//);
// If your API supports ApiKey, you can override the default values.
// "apiKeyIn" can either be "query" or "header"
//
//c.EnableApiKeySupport("apiKey", "header");
});
}
}
}
4 items are still needed:
Uncomment the line in the SwaggerConfig Register() method that says c.IncludeXmlComments(GetXmlCommentsPath());
Add a private static string property to your SwaggerConfig class that specifies the path for the XML
private static string GetXmlCommentsPath()
{
var path = string.Format(#"{0}bin\StarterAPI.XML", System.AppDomain.CurrentDomain.BaseDirectory);
return path;
}
Generate XML by changing your build options as follows: in Project Properties, Build tab, Output section CHECK the XML documentation file and make it read: bin\StarterAPI.xml Notice that it is the same path as generated in the previous step...this links the build XML creation to the SwaggerConfig consumer.
Lastly, annotate your controller methods with /// tags , [HttpGet] annotations and [Route("")] annotations. The sample below looks mostly complete:
// GET api/Stuff/17205
/// <summary>
/// Returns Stuff record based on supplied Stuff_Id
/// </summary>
/// <param name="stuffId"></param>
/// <returns></returns>
[HttpGet]
[Route("api/Stuff/{stuffId}")]
public Stuff_Request Get(int stuffId) {
With Swashbuckle version 5.6.0 (actually you probably need only Swashbuckle.Core nuget package) you can configure it very easy.
Create SwaggerConfig class that looks like this:
public static class SwaggerConfig
{
public static void Register(HttpConfiguration configuration)
{
configuration.EnableSwagger(Configure).EnableSwaggerUi(ConfigureUi);
}
static void Configure(SwaggerDocsConfig config)
{
config.SingleApiVersion("V1", "MichalBialecki.com.Swagger.Example");
config.UseFullTypeNameInSchemaIds();
}
static void ConfigureUi(SwaggerUiConfig config)
{
config.DocExpansion(DocExpansion.None);
config.DisableValidator();
}
}
You also need to register it in Startup.cs:
SwaggerConfig.Register(config);
And that's it. It works for me.

Automatically detect when storing an object with ServiceStack.Redis

I am looking for a way to subscribe to events like Storing a specific object type to ServiceStack.Redis.
For example I may
using (var redisClient = new RedisClient())
using (var redisMyObjects = redisClient.As<MyObject>())
{
redisMyObjects.Store(myObject);//<-- I want this to trigger an event somehow
}
Is there anything like a OnStore event which I can hook too, anything out of the box? if not, is there any recommendation about how this should be done?
I don't think there is anything you can hook into (could be wrong).
Two options that came to mind:
1 - Make an extension method
2 - Publish a message to store your object and have a handler that listens for a response and does something. This is probably overkill since it's heading into the publish/subscribe realm. But, I think, worth looking into. (Basic example here and see Pub/Sub here).
Extension Method
public static class RedisClientExtensions
{
public static void StoreWithTrigger<T>(this IRedisTypedClient<T> redisClient, T value, Action<T> trigger)
{
redisClient.Store(value);
trigger(value);
}
}
Using ExtensionMethod
public void MyMethod()
{
using (var redisClient = new RedisClient())
using (var redisMyObjects = redisClient.As<MyObject>())
{
redisMyObjects.StoreWithTrigger<MyObject>(new MyObject(), TriggerEvent);//<-- I want this to trigger an event somehow
}
}
private void TriggerEvent<T>(T value)
{
//dosomething
}
Hope this gives you some ideas.

Executing ErrorFilter from inside action method catch block

I am using a global ErrorHandler filter to log unhandled exceptions, but I would also like to be able to use this same handler to log handled exceptions from within a catch block.
I would like to access the global filters and execute the ErrorHandler as configured, but I'm having some difficulty doing this. I tried the following, but Resharper is telling me that the filter can't ever be my ErrorLogFilter.
foreach (var filter in GlobalFilters.Filters)
{
if (filter is ErrorLogFilter) // Static analysis tells me this can never be an ErrorLogFilter
{
// want to execute the filter
}
}
ErrorLogFilter is added to the global filters like this:
GlobalFilters.Filters.Add(new ErrorLogFilter(provider));
ErrorLogFilter (as part of Elfar, similar to Elmah, but for MVC) is defined as:
public class ErrorLogFilter : FilterAttribute, IExceptionFilter
Any suggestions on how to accomplish this?
GlobalFilters.Filters is a GlobalFilterCollection which contains Filter's which are wrappers around the added IMvcFilters.
Try this instead:
foreach (var filter in GlobalFilters.Filters)
{
if (filter.Instance is ErrorLogFilter)
{
// want to execute the filter
}
}
So Reshaper is correct signaled that there is no ErrorLogFilter in GlobalFilters.Filters.

Resources