Mediatr implementation in ASP.Net WebApi2 - asp.net-web-api2

I am trying to implement mediatr pattern in ASP.Net Web Api.
Getting the following error:
Handler was not found for request of type
MediatR.IRequestHandler`2[Orion_API.Campaigns.GetCampaigns.GetCampaignsRequest,System.Collections.Generic.List`1[Orion_API.Campaigns.GetCampaigns.GetCampaignsResponse]].
Register your handlers with the container.
My bootstrapper class is as follows:
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterGeneric(typeof(GenericRepository<>))
.As(typeof(IGenericRepository<>))
.InstancePerRequest();
builder.RegisterType<Mediator>().As<IMediator>().InstancePerLifetimeScope();
builder.Register<ServiceFactory>(context =>
{
var componentContext = context.Resolve<IComponentContext>();
return t => {
object o;
return componentContext.TryResolve(t, out o) ? o : null;
};
});
//Set the dependency resolver to be Autofac.
Container = builder.Build();

builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterGeneric(typeof(GenericRepository<>))
.As(typeof(IGenericRepository<>))
.InstancePerRequest();
builder.RegisterType<Mediator>().As<IMediator>().InstancePerLifetimeScope();
builder.Register<ServiceFactory>(context =>
{
var componentContext = context.Resolve<IComponentContext>();
return t => {
object o;
return componentContext.TryResolve(t, out o) ? o : null;
};
});

Related

Why is JobConsumer not being hit/run?

I am trying out the new MassTransit IJobConsumer implementation, and although I've tried to follow the documentation, the JobConsumer I have written is never being run/hit.
I have:
created the JobConsumer which has a run method that runs the code I need it to
public class CalculationStartRunJobConsumer : IJobConsumer<ICalculationStartRun>
{
private readonly ICalculationRunQueue runQueue;
public CalculationStartRunJobConsumer(ICalculationRunQueue runQueue)
{
this.runQueue = runQueue;
}
public Task Run(JobContext<ICalculationStartRun> context)
{
return Task.Run(
() =>
{
var longRunningJob = new LongRunningJob<ICalculationStartRun>
{
Job = context.Job,
CancellationToken = context.CancellationToken,
JobId = context.JobId,
};
runQueue.StartSpecial(longRunningJob);
},
context.CancellationToken);
}
}
I have registered that consumer trying both ConnectReceiveEndpoint and AddConsumer
Configured the ServiceInstance as shown in the documentation
services.AddMassTransit(busRegistrationConfigurator =>
{
// TODO: Get rid of this ugly if statement.
if (consumerTypes != null)
{
foreach (var consumerType in consumerTypes)
{
busRegistrationConfigurator.AddConsumer(consumerType);
}
}
if(requestClientType != null)
{
busRegistrationConfigurator.AddRequestClient(requestClientType);
}
busRegistrationConfigurator.UsingRabbitMq((context, cfg) =>
{
cfg.UseNewtonsoftJsonSerializer();
cfg.UseNewtonsoftJsonDeserializer();
cfg.ConfigureNewtonsoftJsonSerializer(settings =>
{
// The serializer by default omits fields that are set to their default value, but this causes unintended effects
settings.NullValueHandling = NullValueHandling.Include;
settings.DefaultValueHandling = DefaultValueHandling.Include;
return settings;
});
cfg.Host(
messagingHostInfo.HostAddress,
hostConfigurator =>
{
hostConfigurator.Username(messagingHostInfo.UserName);
hostConfigurator.Password(messagingHostInfo.Password);
});
cfg.ServiceInstance(instance =>
{
instance.ConfigureJobServiceEndpoints(serviceCfg =>
{
serviceCfg.FinalizeCompleted = true;
});
instance.ConfigureEndpoints(context);
});
});
});
Seen that the queue for the job does appear in the queue for RabbitMQ
When I call .Send to send a message to that queue, it does not activate the Run method on the JobConsumer.
public async Task Send<T>(string queueName, T message) where T : class
{
var endpointUri = GetEndpointUri(messagingHostInfo.HostAddress, queueName);
var sendEndpoint = await bus.GetSendEndpoint(endpointUri);
await sendEndpoint.Send(message);
}
Can anyone help?
Software
MassTransit 8.0.2
MassTransit.RabbitMq 8.0.2
MassTransit.NewtonsoftJson 8.0.2
.NET6
Using in-memory for JobConsumer
The setup of any type of repository for long running jobs is missing. We needed to either:
explicitly specify that it was using InMemory (missing from the docs)
Setup saga repositories using e.g. EF Core.
As recommended by MassTransit, we went with the option of setting up saga repositories by implementing databases and interacting with them using EF Core.

How to override route in .ASP.NET Core 5?

I want to override route in ASP.NET Core 5
I tried this one but it is not working
var lastExistingRoute= routeBuilder.Routes.FirstOrDefault(x => ((Route)x).Name == "HomePage");
routeBuilder.Routes.Remove(lastExistingRoute);
routeBuilder.MapRoute("HomePage", "",
new { controller = "CustomPage", action = "Homepage", });
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", });
Created same route with high different display order is worked for me
And good this is it not throwing any exception on inserting new route with the same name
public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder)
{
endpointRouteBuilder.MapControllerRoute("NewCheckout", "onepagecheckout",
new { controller = "NewCheckout", action = "OnePageCheckout" });
}
return string.Empty;
}
public int Priority
{
get
{
return 100;
}
}
}

'Missing type map configuration or unsupported mapping.' Automapperexception in ASP.NET Core WebApi

I'm trying to add AutoMapper to a API (built using ASP.NET Core 3) but it gives me the 'Missing type map configuration or unsupported mapping.'-exception and my google-searches doesn't help me at all... :).
The exception is thrown (as described below) in "GetAllObject1"-method
This is my current setup:
[HttpGet]
public IActionResult GetAllObject1()
{
var object1Items = _myService.GetAllObject1();
Object1ViewModel ouViewModel = _mapper.Map<Object1ViewModel>(Object1Items); // <= This line gives the exception above!!
return Ok(ouViewModel);
}
"AutoMapping.cs":
namespace DataAccess.AutoMapper
{
public class AutoMapping : Profile
{
public AutoMapping()
{
CreateMap < KollOrganizationalUnit, KollOrganizationalUnitViewModel>();
}
}
}
"Startup.cs":
public virtual void ConfigureServices(IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
})
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.IgnoreNullValues = true;
});
services.AddDbContext<RepositoryContext>(opts => opts.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.RegisterDAL();
services.RegisterBizServices();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "API",
Description = "Integration API for XXX",
TermsOfService = new Uri("https://www.xxx.se/terms-of-service"),
Contact = new OpenApiContact()
{
Name = "Integrationcontact",
Email = "integration#xxx.se",
Url = new Uri("https://www.xxx.se")
},
});
});
services.AddAutoMapper(typeof(Startup).Assembly);
}
Am I missing something obious here?
It's hard to say without knowing the return type of _myService.GetAllObject1(), but it seems like it's returning a collection of Object1Unit. If that's the case, you can try something like:
IEnumerable<Object1ViewModel> ouViewModels = Object1Items.Select(x => _mapper.Map<Object1ViewModel>(x));
return Ok(ouViewModels);
If you're trying to map a collection to a single item, you have to tell AutoMapper how to do that or you will get the exception you're seeing (your AutoMapping class only creates a map from a single item to a single item).

Unable to create multiple OpenApi specifications with Swashbuckle

I am building a solution with Asp.Net Boilerplate / Asp.Net Zero
I have created two OpenApi specifications (HostApiv1 and TenantApiv1) as follows in Startup.cs:
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("HostApiv1", new Info { Title = "Host API v1", Version = "v1" });
options.SwaggerDoc("TenantApiv1", new Info { Title = "Tenant API v1", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.IgnoreObsoleteActions();
options.IgnoreObsoleteProperties();
options.OrderActionsBy((apiDesc) => $"{apiDesc.RelativePath}");
options.DescribeAllEnumsAsStrings();
});
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint(_appConfiguration["App:HostApiSwaggerEndPoint"], "Host API v1");
options.SwaggerEndpoint(_appConfiguration["App:TenantApiSwaggerEndPoint"], "Tenant API v1");
//...
});
However, when I decorate my AppService classes with [ApiExplorerSettings(GroupName = "HostApiv1")], the grouping is ignored and the tag (AppService controller), along with all of its operations (actions / methods), still appear under both documents.
Any idea what is wrong, or how I can debug it?
Swashbuckle depends on ApiExplorer, and the use of the ApiExplorer attribute limits us to specifying only a single groupname per controller / action. ABP service proxies are generated via NSwag for angular project, and it seems that during this process a dependency is broken.
The workaround is to create a custom Attribute for delimiting one-or-more groupnames for an appservice controller or action, and subsequently use reflection in the DocInclusionPredicateFunction option to retrieve the groupnames for an action or its containing controller.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class SwaggerDocAttribute: Attribute
{
public SwaggerDocAttribute(params string[] includeInDocuments)
{
IncludeInDocuments = includeInDocuments;
}
public string[] IncludeInDocuments { get; }
}
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (!apiDesc.ActionDescriptor.IsControllerAction())
{
return false;
}
apiDesc.TryGetMethodInfo(out MethodInfo methodInfo);
var actionDocs = methodInfo.GetCustomAttributes<SwaggerDocAttribute>()
.SelectMany(a => a.IncludeInDocuments);
var controllerDocs = methodInfo.DeclaringType.GetCustomAttributes<SwaggerDocAttribute>()
.SelectMany(a => a.IncludeInDocuments);
switch (docName)
{
case "HostApiv1":
return apiDesc.GroupName == null ||
actionDocs.Contains("HostApiv1") ||
controllerDocs.Contains("HostApiv1");
case "TenantApiv1":
return apiDesc.GroupName == null ||
actionDocs.Contains("TenantApiv1") ||
controllerDocs.Contains("TenantApiv1");
default:
return true;
}
});
Usage
[DisableAuditing]
[AbpAuthorize(AppPermissions.HostSpecific.Dashboard.Access)]
//[ApiExplorerSettings(GroupName = "HostApiv1")] // <== Don't use this
[SwaggerDoc("HostApiv1")] // <== Use this in stead
public class MyDemoAppService : ZenDetectAppServiceBase, IHostDashboardAppService
{
//...
}

Yii: Catching all exceptions for a specific controller

I am working on a project which includes a REST API component. I have a controller dedicated to handling all of the REST API calls.
Is there any way to catch all exceptions for that specific controller so that I can take a different action for those exceptions than the rest of the application's controllers?
IE: I'd like to respond with either an XML/JSON formatted API response that contains the exception message, rather than the default system view/stack trace (which isn't really useful in an API context). Would prefer not having to wrap every method call in the controller in its own try/catch.
Thanks for any advice in advance.
You can completely bypass Yii's default error displaying mechanism by registering onError and onException event listeners.
Example:
class ApiController extends CController
{
public function init()
{
parent::init();
Yii::app()->attachEventHandler('onError',array($this,'handleError'));
Yii::app()->attachEventHandler('onException',array($this,'handleError'));
}
public function handleError(CEvent $event)
{
if ($event instanceof CExceptionEvent)
{
// handle exception
// ...
}
elseif($event instanceof CErrorEvent)
{
// handle error
// ...
}
$event->handled = TRUE;
}
// ...
}
I wasn't able to attach events in controller, and I did it by redefinition CWebApplication class:
class WebApplication extends CWebApplication
{
protected function init()
{
parent::init();
Yii::app()->attachEventHandler('onError',array($this, 'handleApiError'));
Yii::app()->attachEventHandler('onException',array($this, 'handleApiError'));
}
/**
* Error handler
* #param CEvent $event
*/
public function handleApiError(CEvent $event)
{
$statusCode = 500;
if($event instanceof CExceptionEvent)
{
$statusCode = $event->exception->statusCode;
$body = array(
'code' => $event->exception->getCode(),
'message' => $event->exception->getMessage(),
'file' => YII_DEBUG ? $event->exception->getFile() : '*',
'line' => YII_DEBUG ? $event->exception->getLine() : '*'
);
}
else
{
$body = array(
'code' => $event->code,
'message' => $event->message,
'file' => YII_DEBUG ? $event->file : '*',
'line' => YII_DEBUG ? $event->line : '*'
);
}
$event->handled = true;
ApiHelper::instance()->sendResponse($statusCode, $body);
}
}
In index.php:
require_once(dirname(__FILE__) . '/protected/components/WebApplication.php');
Yii::createApplication('WebApplication', $config)->run();
You can write your own actionError() function per controller. There are several ways of doing that described here
I'm using the following Base controller for an API, it's not stateless API, mind you, but it can serve just aswell.
class BaseJSONController extends CController{
public $data = array();
public $layout;
public function filters()
{
return array('mainLoop');
}
/**
* it all starts here
* #param unknown_type $filterChain
*/
public function filterMainLoop($filterChain){
$this->data['Success'] = true;
$this->data['ReturnMessage'] = "";
$this->data['ReturnCode'] = 0;
try{
$filterChain->run();
}catch (Exception $e){
$this->data['Success'] = false;
$this->data['ReturnMessage'] = $e->getMessage();
$this->data['ReturnCode'] = $e->getCode();
}
echo json_encode($this->data);
}
}
You could also catch dbException and email those, as they're somewhat critical and can show underlying problem in the code/db design.
Add this to your controller:
Yii::app()->setComponents(array(
'errorHandler'=>array(
'errorAction'=>'error/error'
)
));