webapi receiving null parameter - asp.net-mvc-4

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.

Related

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

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")]

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.

How to pass endpoint details dynamically from configuration?

I followed article to include WCF service into my ASP.NET core application.
Looking at below line in reference.cs, it seems default endpoint configuration is hard-coded inside reference.cs.
return new System.ServiceModel.EndpointAddress("http://localhost:49945/SimpleService.svc");
This is how I can create my client in asp.net core controller-
BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
EndpointAddress endpointAddress = new EndpointAddress("http://localhost:49945/SimpleService.svc");
wcfClient = new SimpleServiceClient(basicHttpBinding, endpointAddress);
So my questions are-
Where should I maintain endpoint details so that it can be easily configurable during deployment?
How I can pass endpoint details (address and binding) dynamically from configuration file (appsetting.json)?
Generated Reference.cs file looks like follows-
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// //
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace SimpleServiceReference
{
using System.Runtime.Serialization;
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "0.5.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="CompositeType", Namespace="http://schemas.datacontract.org/2004/07/SimpleService")]
public partial class CompositeType : object
{
private bool BoolValueField;
private string StringValueField;
[System.Runtime.Serialization.DataMemberAttribute()]
public bool BoolValue
{
get
{
return this.BoolValueField;
}
set
{
this.BoolValueField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public string StringValue
{
get
{
return this.StringValueField;
}
set
{
this.StringValueField = value;
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "0.5.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="SimpleServiceReference.ISimpleService")]
public interface ISimpleService
{
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ISimpleService/GetData", ReplyAction="http://tempuri.org/ISimpleService/GetDataResponse")]
System.Threading.Tasks.Task<string> GetDataAsync(int value);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ISimpleService/GetDataUsingDataContract", ReplyAction="http://tempuri.org/ISimpleService/GetDataUsingDataContractResponse")]
System.Threading.Tasks.Task<SimpleServiceReference.CompositeType> GetDataUsingDataContractAsync(SimpleServiceReference.CompositeType composite);
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "0.5.0.0")]
public interface ISimpleServiceChannel : SimpleServiceReference.ISimpleService, System.ServiceModel.IClientChannel
{
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "0.5.0.0")]
public partial class SimpleServiceClient : System.ServiceModel.ClientBase<SimpleServiceReference.ISimpleService>, SimpleServiceReference.ISimpleService
{
/// <summary>
/// Implement this partial method to configure the service endpoint.
/// </summary>
/// <param name="serviceEndpoint">The endpoint to configure</param>
/// <param name="clientCredentials">The client credentials</param>
static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials);
public SimpleServiceClient() :
base(SimpleServiceClient.GetDefaultBinding(), SimpleServiceClient.GetDefaultEndpointAddress())
{
this.Endpoint.Name = EndpointConfiguration.BasicHttpBinding_ISimpleService.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public SimpleServiceClient(EndpointConfiguration endpointConfiguration) :
base(SimpleServiceClient.GetBindingForEndpoint(endpointConfiguration), SimpleServiceClient.GetEndpointAddress(endpointConfiguration))
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public SimpleServiceClient(EndpointConfiguration endpointConfiguration, string remoteAddress) :
base(SimpleServiceClient.GetBindingForEndpoint(endpointConfiguration), new System.ServiceModel.EndpointAddress(remoteAddress))
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public SimpleServiceClient(EndpointConfiguration endpointConfiguration, System.ServiceModel.EndpointAddress remoteAddress) :
base(SimpleServiceClient.GetBindingForEndpoint(endpointConfiguration), remoteAddress)
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public SimpleServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
public System.Threading.Tasks.Task<string> GetDataAsync(int value)
{
return base.Channel.GetDataAsync(value);
}
public System.Threading.Tasks.Task<SimpleServiceReference.CompositeType> GetDataUsingDataContractAsync(SimpleServiceReference.CompositeType composite)
{
return base.Channel.GetDataUsingDataContractAsync(composite);
}
public virtual System.Threading.Tasks.Task OpenAsync()
{
return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginOpen(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndOpen));
}
public virtual System.Threading.Tasks.Task CloseAsync()
{
return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginClose(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndClose));
}
private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.BasicHttpBinding_ISimpleService))
{
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
return result;
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.BasicHttpBinding_ISimpleService))
{
return new System.ServiceModel.EndpointAddress("http://localhost:49945/SimpleService.svc");
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
private static System.ServiceModel.Channels.Binding GetDefaultBinding()
{
return SimpleServiceClient.GetBindingForEndpoint(EndpointConfiguration.BasicHttpBinding_ISimpleService);
}
private static System.ServiceModel.EndpointAddress GetDefaultEndpointAddress()
{
return SimpleServiceClient.GetEndpointAddress(EndpointConfiguration.BasicHttpBinding_ISimpleService);
}
public enum EndpointConfiguration
{
BasicHttpBinding_ISimpleService,
}
}
}
I needed the same thing in the past, and ended up storing the connection details for a WCF service in the app's options. I stored the details in the appsettings.json file, created an Options class, and registered it with the services setup logic so I could request it when creating the WCF service.
Bare with my code, I just whipped this up quickly. I have not tested it for common errors like missing braces, semicolons, or misspellings :-P
Options class
public class MyServiceOptions
{
public string EndpointUrl {get;set;}
}
Excerpt from startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyServiceOptions>Configuration.GetSection("MyService"));
//Other calls as needed...
}
appsettings.json
{
"MyService": {
"EndpointUrl": "http://localhost:49945/SimpleService.svc"
}
}
Then you can access your options by requesting an instance of IOptions<MyServiceOptions> from the dependency injection container in a variaty of ways.
public class MyController : Controller
{
//Option 1, in controller constructor
private IOptions<MyServiceOptions> myOptions;
public MyController(IOptions<MyServiceOptions> myOptions1)
{
myOptions = myOptions1
}
//Option 2, in action method signature
public IActionResult MyAction([FromServices]IOptions<MyServiceOptions> myOptions2)
{
//Option 3, directly
var myOptions3 = HttpContext.RequestServices.GetService<IControllerFactory>();
//NOTE: The GetService<>() method is an extension method from the Microsoft.Extensions.DependencyInjection namespace
BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
EndpointAddress endpointAddress = new EndpointAddress(myOptions.Value.EndpointUrl);
wcfClient = new SimpleServiceClient(basicHttpBinding, endpointAddress);
}
}

WebApiContrib Jsonp and Attribute Routing

Per the WebApiContrib.Formatting.Jsonp GitHub readme, it appears that in the RouteConfig.cs this should be entered:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{format}",
defaults: new { id = RouteParameter.Optional, format = RouteParameter.Optional }
);
I currently don't have a RouteConfig.cs file in my AppStart. I created it using the Web API 2 template and I don't think I changed anything structurally. I do have a WebApiConfig.cs where I have set:
public static void Register (HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
}
how do I include it such that all routes have the ability to return Jsonp?
You could create a custom route attribute which implements IHttpRouteInfoProvider (which Web API route builder looks for when adding routes to route table) and then modify the template that is being generated by appending {format}
Example:
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
[CustomRoute(Order = 1)]
public IEnumerable<string> GetAll()
{
return new string[] { "value1", "value2" };
}
[CustomRoute("{id}")]
public string GetSingle(int id)
{
return "value";
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public sealed class CustomRouteAttribute : Attribute, IHttpRouteInfoProvider
{
public CustomRouteAttribute()
{
Template = String.Empty;
}
public CustomRouteAttribute(string template)
{
if (template == null)
{
throw new ArgumentNullException("template");
}
if (template == string.Empty)
{
Template = template + "{format?}";
}
else
{
Template = template.TrimEnd('/') + "/{format?}";
}
}
public string Name { get; set; }
public int Order { get; set; }
public string Template { get; private set; }
}
I found this comment in a pull request but I don't understand if this is yet implemented into the production package nor if it got pulled at all.
If you are using Attribute Routing, you should add "/{format}" after each route if you plan to use the URI mapping for jsonp, e.g. [Route("api/value/{id:int}/{format?}")]. If you will require the Content-Type header to specify text/javascript, then you can leave your routes alone. (See the sample applications for examples.)

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);
}
}