asp.net core 2.1 odata use different name of entity in the route - asp.net-core

I have a long name of of entity in my code EmployeTraining which used as entity in OData and with same name for the controller.
Startup.cs
app.UseMvc(routeBuilder=>
{
routeBuilder.Expand().Select().Count().OrderBy().Filter().MaxTop(null);
routeBuilder.MapODataServiceRoute("EmployeTraining", "odata/v1", EdmModelBuilder.GetEdmModelEmploye());
});
EdmModelBuilder.cs
public static IEdmModel GetEdmModelEmployes()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<EmployeTraining>("EmployeTraining");
return builder.GetEdmModel();
}
EmployeTrainingControllers.cs
public class EmployeTrainingController : ODataController
{
internal IEmployeService ServiceEmploye { get; set; }
public EmployesController(IEmployeService serviceEmploye)
{
ServiceEmploye = serviceEmploye;
}
//// GET api/employes
[HttpGet]
[MyCustomQueryable()]
public IQueryable<EmployeTraining> Get()
{
return ServiceEmploye.GetListeEmployes();
}
}
To call my service it works only through this URL: https://{server}/odata/v1/rh/employetraining
but I need to use this https://{server}/odata/v1/rh/employe-training
any help please.

For such scenario,change like below:
1.Change the entityset name:
public static class EdmModelBuilder
{
public static IEdmModel GetEdmModelEmployes()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<EmployeTraining>("employe-training");
return builder.GetEdmModel();
}
}
2.Add the attribute:
public class EmployeTrainingController : ODataController
{
[HttpGet]
[ODataRoute("employe-training")]
//[MyCustomQueryable()]
public IQueryable<EmployeTraining> Get()
{
return ServiceEmploye.GetListeEmployes();
}
}
3.Startup.cs:
app.UseMvc(routeBuilder=>
{
routeBuilder.Expand().Select().Count().OrderBy().Filter().MaxTop(null);
routeBuilder.MapODataServiceRoute("EmployeTraining", "odata/v1/rh", EdmModelBuilder.GetEdmModelEmploye());
});
Request the url:https://{server}/odata/v1/rh/employe-training

The Reason why is working using https://{server}/odata/v1/rh/employetraining is because is the Get method of the EmployeTrainingController Controller.
You should be able to change that behaibour if you modify the [HttpGet] on the Get method to [HttpGet("employe-training")]

Related

Wiring up validation in MediatR and ASP.NET Core using autofac

I've just started to use MediatR in an asp.net core project and am struggling to wire up validation ...
Here's my controller:
public class PersonController : Controller
{
IMediator mediator;
public PersonController(IMediator mediator)
{
this.mediator = mediator;
}
[HttpPost]
public async Task<ActionResult> Post([FromBody]CreatePerson model)
{
var success = await mediator.Send(model);
if (success)
{
return Ok();
}
else
{
return BadRequest();
}
}
}
... and the CreatePerson command, validation (via FluentValidation) and request handler:
public class CreatePerson : IRequest<bool>
{
public string Title { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
}
public class CreatePersonValidator : AbstractValidator<CreatePerson>
{
public CreatePersonValidator()
{
RuleFor(m => m.FirstName).NotEmpty().Length(1, 50);
RuleFor(m => m.Surname).NotEmpty().Length(3, 50);
}
}
public class CreatePersonHandler : IRequestHandler<CreatePerson, bool>
{
public CreatePersonHandler()
{
}
public bool Handle(CreatePerson message)
{
// do some stuff
return true;
}
}
I have this generic validation handler:
public class ValidatorHandler<TRequest, TResponse> : IRequestHandler<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
private readonly IRequestHandler<TRequest, TResponse> inner;
private readonly IValidator<TRequest>[] validators;
public ValidatorHandler(IRequestHandler<TRequest, TResponse> inner, IValidator<TRequest>[] validators)
{
this.inner = inner;
this.validators = validators;
}
public TResponse Handle(TRequest message)
{
var context = new ValidationContext(message);
var failures = validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
throw new ValidationException(failures);
return inner.Handle(message);
}
}
... but I'm struggling to wire the validation up correctly in Startup.ConfigureServices using autofac:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
var builder = new ContainerBuilder();
builder.Register<SingleInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
builder.Register<MultiInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => (IEnumerable<object>)c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
});
builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly).AsImplementedInterfaces();
builder.RegisterAssemblyTypes(typeof(CreatePersonHandler).GetTypeInfo().Assembly).AsClosedTypesOf(typeof(IRequestHandler<,>));
builder.RegisterGenericDecorator(typeof(ValidatorHandler<,>), typeof(IRequestHandler<,>), "Validator").InstancePerLifetimeScope();
builder.Populate(services);
var container = builder.Build();
return container.Resolve<IServiceProvider>();
}
When I run the app and POST /api/person
{
"title": "Mr",
"firstName": "Paul",
"surname": ""
}
I get a 200.
CreatePersonHandler.Handle() was called but CreatePersonValidator() is never called.
Am i missing something in Startup.ConfigureServices()?
I suggest that you read the official documentation on how to wire up decorators in Autofac.
Decorators use named services to resolve the decorated services.
For example, in your piece of code:
builder.RegisterGenericDecorator(
typeof(ValidatorHandler<,>),
typeof(IRequestHandler<,>),
"Validator").InstancePerLifetimeScope();
you're instructing Autofac to use ValidationHandler<,> as a decorator to IRequestHandler<,> services that have been registered with the Validator name, which is probably not what you want.
Here's how you could get it working:
// Register the request handlers as named services
builder
.RegisterAssemblyTypes(typeof(CreatePersonHandler).GetTypeInfo().Assembly)
.AsClosedTypesOf(typeof(IRequestHandler<,>))
.Named("BaseImplementation");
// Register the decorators on top of your request handlers
builder.RegisterGenericDecorator(
typeof(ValidatorHandler<,>),
typeof(IRequestHandler<,>),
fromKey: "BaseImplementation").InstancePerLifetimeScope();
I find specifying the name of the fromKey parameter helps in understanding how decorators work with Autofac.

Swashbuckle not displaying methods that have a collection as a paramater

I have a Web API (WebApi 2). I am trying to use Swashbuckle for documentation. I have a GET method that takes a List as a paramater. The method works but does not show up in the Swashbuckle documentation.
[RoutePrefix("myroute")]
public class MyController : ApiController
{
[HttpGet]
[Route("{foo}/{bar}")]
public async Task<IHttpActionResult> Get([FromUri]List<string> foo, string bar)
{
return Ok();
}
}
How do I get a List or array to work with Swashbuckle?
UPDATE
Here is my swagger config:
public class SwaggerConfig
{
public static void Register(HttpConfiguration config)
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
config
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "ZipCodeWebApi.API");
c.IncludeXmlComments(string.Format(#"{0}\App_Data\ZipCodeWebApi.API.XML", System.AppDomain.CurrentDomain.BaseDirectory));
c.DescribeAllEnumsAsStrings();
})
.EnableSwaggerUi(c =>
{
});
}
}
As you are already saying [FromUri], your action method will show up in swagger only if you remove the Route attribute.
[HttpGet]
public async Task<IHttpActionResult> Get([FromUri]List<string> foo, string bar)
{
return Ok();
}

How to invoke a View Component from controller

Is it possible to invoke a View Component from controller and render it to a string? I am really looking for some code example for this. Any help will be much appreciated.
As of beta7 it is now possible to return a ViewComponent directly from a controller. Check the MVC/Razor section of the announcement
The new ViewComponentResult in MVC makes it easy to return the result
of a ViewComponent from an action. This allows you to easily expose
the logic of a ViewComponent as a standalone endpoint.
So now the code for returning the sample view component just needs to be:
public class HomeController : Controller
{
public IActionResult Index()
{
return ViewComponent("My");
}
}
Please refer to example from official ASP.NET article on ViewComponent
In their example, the view component is called directly from the controller as follows:
public IActionResult IndexVC()
{
return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}
You can do that but you have to apply following thing as It is render by DefaultViewComponentHelper.
You have to create instance of this and to create that you need IViewComponentSelector and IViewComponentInvokerFactory.
To do this I have done following thing.
public class HomeController : Controller
{
Microsoft.AspNet.Mvc.DefaultViewComponentHelper helper = null;
Microsoft.AspNet.Mvc.Razor.RazorView razorView = null;
public HomeController(IViewComponentSelector selector,IViewComponentInvokerFactory factory,IRazorPageFactory razorPageFactory,IRazorPageActivator pageActivator,IViewStartProvider viewStartProvider)
{
helper = new DefaultViewComponentHelper(selector, factory);
razorView = new Microsoft.AspNet.Mvc.Razor.RazorView(razorPageFactory, pageActivator, viewStartProvider);
}
public IActionResult Index()
{
ViewContext context = new ViewContext(ActionContext, razorView, ViewData, null);
helper.Contextualize(context);
string st1 = helper.Invoke("My", null).ToString();
return View();
}
}
Here is my sample View Component.
public class MyViewComponent : ViewComponent
{
public MyViewComponent()
{
}
public IViewComponentResult Invoke()
{
return Content("This is test");
}
}
Here's a tag helper that I created to embed components via HTML like syntax. Invoking from a TagHelper like this should closely match invoking from a Controller.
ViewComponent Tag Helper
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace TagHelperSamples.Web
{
[HtmlTargetElement("component")]
public class ComponentTagHelper : TagHelper
{
private DefaultViewComponentHelper _componentHelper;
[HtmlAttributeName("name")]
public string Name { get; set; }
[HtmlAttributeName("params")]
public object Params { get; set; }
[ViewContextAttribute] // inform razor to inject
public ViewContext ViewContext { get; set; }
public ComponentTagHelper(IViewComponentHelper componentHelper)
{
_componentHelper = componentHelper as DefaultViewComponentHelper;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
_componentHelper.Contextualize(ViewContext);
output.Content.AppendHtml(
await _componentHelper.InvokeAsync(Name, Params)
);
}
}
}
Usage
<component name="RecentComments" params="new { take: 5, random: true }"></component>
Code from dotnetstep's answer updated for MVC 6.0.0-beta4 (VS2015 RC):
public class HomeController : Controller
{
Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentHelper helper = null;
public HomeController(IViewComponentDescriptorCollectionProvider descriptorProvider, IViewComponentSelector selector, IViewComponentInvokerFactory invokerFactory)
{
helper = new DefaultViewComponentHelper(descriptorProvider, selector, invokerFactory);
}
public IActionResult Index()
{
ViewContext context = new ViewContext(ActionContext, null, ViewData, null, null);
helper.Contextualize(context);
string st1 = helper.Invoke("My", null).ToString();
return View();
}
}
Based on https://gist.github.com/pauldotknopf/b424e9b8b03d31d67f3cce59f09ab17f
public class HomeController : Controller
{
public async Task<string> RenderViewComponent(string viewComponent, object args)
{
var sp = HttpContext.RequestServices;
var helper = new DefaultViewComponentHelper(
sp.GetRequiredService<IViewComponentDescriptorCollectionProvider>(),
HtmlEncoder.Default,
sp.GetRequiredService<IViewComponentSelector>(),
sp.GetRequiredService<IViewComponentInvokerFactory>(),
sp.GetRequiredService<IViewBufferScope>());
using (var writer = new StringWriter())
{
var context = new ViewContext(ControllerContext, NullView.Instance, ViewData, TempData, writer, new HtmlHelperOptions());
helper.Contextualize(context);
var result = await helper.InvokeAsync(viewComponent, args);
result.WriteTo(writer, HtmlEncoder.Default);
await writer.FlushAsync();
return writer.ToString();
}
}
}
and
public class NullView : IView
{
public static readonly NullView Instance = new();
public string Path => string.Empty;
public Task RenderAsync(ViewContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
return Task.CompletedTask;
}
}

webapi receiving null parameter

I'm attempting to POST an object to a WebAPI controller that accepts a complex type as the parameter but the parameter recived is null. Any ideas why? The request is hitting the WebAPI method properly and the parameter is null.
Model :
namespace DMAX.BLL.MASReports.Models
{
public class StatsCriteria
{
#region Constructors and Methods
public StatsCriteria()
{
}
#endregion
#region Properties and Fields
private string _masnum;
private string _notchosen;
private int _currentPage = 1;
private bool _isPrint = false;
private bool _isEmail = false;
private bool _isAjax = false;
public string Masnums { get {
if (!string.IsNullOrEmpty(_masnum)) {
_masnum = _masnum.Replace("'", "");
if (!string.IsNullOrEmpty(NotChosen)) {
string[] notchosenlist = NotChosen.Split(',');
foreach (var notchosen in notchosenlist) {
_masnum = this.RemoveNotChosen(_masnum, notchosen);
}
}
return _masnum;
}
return null;
}
set { _masnum = value; }
}
public string AgentId { get; set; }
public string LicenseNum { get; set; }
public string AgentFullName { get; set; }
public string HeaderName { get; set; }
#endregion
}
}
}
Here's the code at client : [ The StatsCriteria is part of the project BLL and I am referencing it in MASReports project]
namespace MASReports.Controllers
{
public ActionResult Reports(StatsCriteria criteria)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.PostAsJsonAsync("http://localhost:52765/api/reports", criteria.Masnums.ToString()).Result;
return View("CMAReport", response);
}
}
Here's the signature for my controller in Webapi.
[ The StatsCriteria is part of the project BLL and I have a reference to that project in ReportsAPI project]
[ The CMAReportVM, CMAReport are part of the project BLL and I have a reference to BLL project in ReportsAPIproject]
namespace ReportsAPI.Controllers
{
public class ReportsController : ApiController
{
[HttpPost]
public CMAReportVM Reports([FromBody] StatsCriteria criteria)
{
var cmaReport = Service3.GetCMAReport(criteria.Masnums);
//Create Map to enable mapping business object to View Model
Mapper.CreateMap<CMAReport, CMAReportVM>();
// Maps model to VM model class
var cmaVM = Mapper.Map<CMAReport, CMAReportVM>(cmaReport);
reutn cmaVM;
}
}
}
// and here's my routing:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
and here's my Golbal.asax of Web api
namespace ReportsAPI
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
GlobalConfiguration.Configuration.Formatters.JsonFormatter.MediaTypeMappings.Add(new QueryStringMapping("json", "true", "application/json"));
}
}
}
You are posting a string:
var response = client.PostAsJsonAsync("http://localhost:52765/api/reports", criteria.Masnums.ToString()).Result;
Your controller method expect a StatsCriteria object. Either change the signature of your controller method to accept a string or change the post call.
Assuming that the controller method signature is correct the post should be something like this:
var response = client.PostAsJsonAsync("http://localhost:52765/api/reports", criteria).Result;
If this doesn't help I recommend to use fiddler to check what the message looks like when you post it.

Using MVC 4 & WebAPI, how do I redirect to an alternate service endpoint from within a custom filter?

Thanks for looking.
This is a trivial task when using a normal (not WebAPI) action filter as I can just alter the filterContext.Result property like so:
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary { { "controller", "Home" }, {"action", "Index" } });
Unfortunately, I have to use HttpActionContext for WebAPI, so I can not access filterContext.Result.
So what should I do in place of that? I have the filter set up and it does execute at the appropriate time, I just don't know how to make it prevent execution of the requested service endpoint and instead point to a different one.
Here is my controller:
[VerifyToken]
public class ProductController : ApiController
{
#region Public
public List<DAL.Product.CategoryModel> ProductCategories(GenericTokenModel req)
{
return HelperMethods.Cacheable(BLL.Product.GetProductCategories, "AllCategories");
}
public string Error() //This is the endpoint I would like to reach from the filter!
{
return "Not Authorized";
}
#endregion Public
#region Models
public class GenericTokenModel
{
public string Token { get; set; }
}
#endregion Models
}
Here is my filter:
using System.Web.Http.Controllers;
using ActionFilterAttribute = System.Web.Http.Filters.ActionFilterAttribute;
namespace Web.Filters
{
public class VerifyTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
dynamic test = filterContext.ActionArguments["req"];
if (test.Token != "foo")
{
//How do I redirect from here??
}
base.OnActionExecuting(filterContext);
}
}
}
Any help is appreciated.
The answer in my case was simply to change the Response property of the filterContext rather than to redirect to a different endpoint. This achieved the desired result.
Here is the revised filter:
public class VerifyTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
dynamic test = filterContext.ActionArguments["req"];
if (test.Token != "foo")
{
filterContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
base.OnActionExecuting(filterContext);
}
}