Routing Issue with my asp.net MVC 4 application - asp.net-mvc-4

I'm having hectic last couple of days due to this problem. I'm trying to pass route data to the view as a matter of navigation. However routeInfo contains no route information. i.e. routeInfo.RouteData.Values.Count = 0. I have another application with the same code which is working fine.
I'm not sure what i'm missing here.
Any help would be really appreciated!!
public ActionResult Index(int type)
{
UrlHelper u = new UrlHelper(this.ControllerContext.RequestContext);
string url = u.Action("Action", "Controller", new { type = type }, Request.Url.Scheme);
Uri uri = new Uri(url);
RouteInfo routeInfo = new RouteInfo(uri, HttpContext.Request.ApplicationPath);
Session["_ReturnURL"] = routeInfo.RouteData.Values;
ViewBag.ReturnURL = Helpers.GetSessionKey("_ReturnURL");
return View();
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.RouteExistingFiles = true;
routes.IgnoreRoute("elmah.axd");
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Controller",
url: "Controller/{type}/{action}/{id}",
defaults: new { controller = "Controller", action = "Action", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "CBlah", action = "ABlah", id = UrlParameter.Optional, returnUrl = "~/Blah2/Blah2" }
);
}
public class RouteInfo
{
public RouteData RouteData { get; private set; }
public RouteInfo(RouteData data)
{
RouteData = data;
}
public RouteInfo(Uri uri, string applicationPath)
{
RouteData = RouteTable.Routes.GetRouteData(new InternalHttpContext(uri, applicationPath));
}
private class InternalHttpContext : HttpContextBase
{
private readonly HttpRequestBase _request;
public InternalHttpContext(Uri uri, string applicationPath)
{
_request = new InternalRequestContext(uri, applicationPath);
}
public override HttpRequestBase Request { get { return _request; } }
}
private class InternalRequestContext : HttpRequestBase
{
private readonly string _appRelativePath;
private readonly string _pathInfo;
public InternalRequestContext(Uri uri, string applicationPath)
{
_pathInfo = uri.Query;
if (String.IsNullOrEmpty(applicationPath) || !uri.AbsolutePath.StartsWith(applicationPath, StringComparison.OrdinalIgnoreCase))
_appRelativePath = uri.AbsolutePath.Substring(applicationPath.Length);
else
_appRelativePath = uri.AbsolutePath;
}
public override string AppRelativeCurrentExecutionFilePath { get { return String.Concat("~", _appRelativePath); } }
public override string PathInfo { get { return _pathInfo; } }
}
}

Route.GetRouteData Definition
public override RouteData GetRouteData(HttpContextBase httpContext)
{
string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
RouteValueDictionary values = this._parsedRoute.Match(virtualPath, this.Defaults);
if (values == null)
{
return null;
}
RouteData data = new RouteData(this, this.RouteHandler);
if (!this.ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest))
{
return null;
}
foreach (KeyValuePair<string, object> pair in values)
{
data.Values.Add(pair.Key, pair.Value);
}
if (this.DataTokens != null)
{
foreach (KeyValuePair<string, object> pair2 in this.DataTokens)
{
data.DataTokens[pair2.Key] = pair2.Value;
}
}
return data;
}
The problem lies in this line:
string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
You are appending a query string as the httpContext.Request.PathInfo via:
_pathInfo = uri.Query;
I know for a fact that the Route class does not consider the query string, so appending it to the path is a mistake. There is also some information here that backs up the fact that PathInfo always should return an empty string.
Per MSDN:
For the URL http://www.contoso.com/virdir/page.html/tail, the PathInfo value is /tail.
So, adding a query string is making this line fail (when there is a query string) because _parsedRoute.Match isn't expecting one.
RouteValueDictionary values = this._parsedRoute.Match(virtualPath, this.Defaults);
My guess is that one of your applications "works" because it is not being passed a URL with a query string, and therefore matches correctly. But whatever the case, you should return an empty string from PathInfo in your InternalRequestContext class to make it work 100% of the time (unless you have crazy URLs with dots in them, then you may need to do some extra work).

Related

Display fields (from query) on Swagger UI from complex record

I have a complex record SearchProductsRequest in a GET request that receives the parameters by query (
/v1/products?ids=1,2,3&name=hombre&page=3&pageItems=4&sortField=name&sort=asc ).
app.MapGet(
$"/{ProductCatalogueApi.Version}/products",
(SearchProductsRequest request)
=> ProductApiDelegates.SearchProducts(
request));
In the record, I've implemented the bind async
public static ValueTask<SearchProductsRequest?> BindAsync(HttpContext httpContext, ParameterInfo parameter); and now the parameters from the URL automatically convert the parameters to SearchProductsRequest.
The request is working as intended, but we are using (Swashbuckle -> ) Swagger UI for development.
Swagger UI does not recognize the members from SearchProductsRequest to display them as input boxes. Is there a way to make swagger UI know them and display them so a user consulting the swagger endpoint can pass value through it?
I was hoping to get the following:
Until now, I've only managed to have the fields displayed in swagger if I have all of them in the Map.Get() explicitly.
EDIT:
Adding asked content
Record:
public record SearchProductsRequest
{
public IEnumerable<int>? Ids { get; private set; }
public string? Name { get; private set; }
public PaginationInfoRequest? PaginationInfo { get; private set; }
public SortingInfoRequest? SortingInfo { get; private set; }
public SearchProductsRequest(
IEnumerable<int>? ids,
string? name,
PaginationInfoRequest? PaginationInfo,
SortingInfoRequest? SortingInfo)
{
this.Ids = ids;
this.Name = name;
this.PaginationInfo = PaginationInfo;
this.SortingInfo = SortingInfo;
}
public static ValueTask<SearchProductsRequest?> BindAsync(
HttpContext httpContext,
ParameterInfo parameter)
{
var ids = ParseIds(httpContext);
var name = httpContext?.Request.Query["name"] ?? string.Empty;
PaginationInfoRequest? pagination = null;
SortingInfoRequest? sorting = null;
if (int.TryParse(httpContext?.Request.Query["page"], out var page)
&& int.TryParse(httpContext?.Request.Query["pageItems"], out var pageItems))
{
pagination = new PaginationInfoRequest(page, pageItems);
}
var sortField = httpContext?.Request.Query["sortField"].ToString();
if (!string.IsNullOrEmpty(sortField))
{
sorting = new SortingInfoRequest(
sortField,
httpContext?.Request.Query["sort"].ToString() == "asc");
}
return ValueTask.FromResult<SearchProductsRequest?>(
new SearchProductsRequest(
ids,
name!,
pagination,
sorting));
}
#pragma warning disable SA1011 // Closing square brackets should be spaced correctly
private static int[]? ParseIds(HttpContext httpContext)
{
int[]? ids = null;
var commaSeparatedIds = httpContext?.Request.Query["ids"]
.ToString();
if (!string.IsNullOrEmpty(commaSeparatedIds))
{
ids = commaSeparatedIds
.Split(",")
.Select(int.Parse)
.ToArray() ?? Array.Empty<int>();
}
return ids;
}
#pragma warning restore SA1011 // Closing square brackets should be spaced correctly
}
Delegate:
internal static async Task<IResult> SearchProducts(
ILogger<ProductApiDelegates> logger,
IMapper mapper,
SearchProductsRequest request,
IValidator<SearchProductsRequest> validator,
IProductService productService)
{
using var activity = s_activitySource.StartActivity("Search products");
var validationResult = await validator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.GetErrors();
logger.LogError("Bad Request: {Errors}", errors);
return Results.BadRequest();
}
try
{
logger.LogInformation("Searching product details by name");
var filtersContainer = mapper.Map<SearchProductsFiltersContainer>(request);
var products = await productService.SearchProductsAsync(filtersContainer);
if (products == null)
{
return Results.NotFound();
}
var searchProducts = BuildSearchProducts(mapper, products);
var paginationInfo = await BuildPaginationInfo(filtersContainer, productService);
var response = new SearchProductsResponse(searchProducts, paginationInfo);
return Results.Ok(response);
}
catch (Exception ex)
{
logger.LogError(ex, "Error searching the products");
return Results.Problem();
}
}

How do you manage the visible input fields accepted in an API HttpPost request?

In my API I have a Create method in my controller that accepts all of the models fields, but in the method I'm excluding the ID field since on a create it's generated. But in Swagger it's showing the following.
Is there a way for it not to show the following part?
"id": 0
Is a viewmodel how I should go about this?
I tried the following, but can't get it to work.
public class PartVM
{
public string Name { get; set; }
}
public interface IPartService
{
Task<Part> CreatePart(PartVM part);
Task<IEnumerable<Part>> GetParts();
Task<Part> GetPart(int partId);
}
public class PartService : IPartService
{
private readonly AppDbContext _appDbContext;
public PartService(AppDbContext appDbContext)
{
_appDbContext = appDbContext;
}
public async Task<Part> CreatePart(PartVM part)
{
var _part = new Part()
{
Name = part.Name
};
var result = await _appDbContext.Parts.AddAsync(_part);
await _appDbContext.SaveChangesAsync();
return result.Entity;
}
}
Here's my controller.
[Route("api/[controller]")]
[ApiController]
public class PartsController : ControllerBase
{
private readonly IPartService _partService;
public PartsController(IPartService partService)
{
_partService = partService;
}
[HttpPost]
public async Task<ActionResult<Part>> CreatePart(PartVM part)
{
try
{
if (part == null)
return BadRequest();
var _part = new Part()
{
Name = part.Name
};
var createdPart = await _partService.CreatePart(_part);
return CreatedAtAction(nameof(GetPart),
new { id = createdPart.Id}, createdPart);
}
catch (Exception /*ex*/)
{
return StatusCode(StatusCodes.Status500InternalServerError, "Error creating new record in the database");
}
}
I'm getting a build error saying "CS1503 Argument 1: cannot convert from 'MusicManager.Shared.Part' to 'MusicManager.Server.Data.ViewModels.PartVM'".
It's refering to "_part" in this line "var createdPart = await _partService.CreatePart(_part);".
Any help is appreciated, thank you!
you have a CreatePart method which receives a PartVM model, but you are sending a Part Model to it
change your method to this :
public async Task<Part> CreatePart(Part part)
{
var result = await _appDbContext.Parts.AddAsync(_part);
await _appDbContext.SaveChangesAsync();
return result.Entity;
}

How to make Route based on a sub-domain MVC Core

How to make this
- user1.domain.com goes to user1/index (not inside area)
- user2.domain.com goes to user2/index (not inside area)
I mean's the
user1.domain.com/index
user2.domain.com/index
Are same view but different data depending on user{0}
using MVC Core 2.2
There're several approaches depending on your needs.
How to make this - user1.domain.com goes to user1/index (not inside area) - user2.domain.com goes to user2/index (not inside area)
Rewrite/Redirect
One approach is to rewrite/redirect the url. If you don't like do it with nginx/iis, you could create an Application Level Rewrite Rule. For example, I create a sample route rule for your reference:
internal enum RouteSubDomainBehavior{ Redirect, Rewrite, }
internal class RouteSubDomainRule : IRule
{
private readonly string _domainWithPort;
private readonly RouteSubDomainBehavior _behavior;
public RouteSubDomainRule(string domain, RouteSubDomainBehavior behavior)
{
this._domainWithPort = domain;
this._behavior = behavior;
}
// custom this method according to your needs
protected bool ShouldRewrite(RewriteContext context)
{
var req = context.HttpContext.Request;
// only rewrite the url when it ends with target doamin
if (!req.Host.Value.EndsWith(this._domainWithPort, StringComparison.OrdinalIgnoreCase)) { return false; }
// if already rewrite, skip
if(req.Host.Value.Length == this._domainWithPort.Length) { return false; }
// ... add other condition to make sure only rewrite for the routes you wish, for example, skip the Hub
return true;
}
public void ApplyRule(RewriteContext context)
{
if(!this.ShouldRewrite(context)) {
context.Result = RuleResult.ContinueRules;
return;
}
var req = context.HttpContext.Request;
if(this._behavior == RouteSubDomainBehavior.Redirect){
var newUrl = UriHelper.BuildAbsolute( req.Scheme, new HostString(this._domainWithPort), req.PathBase, req.Path, req.QueryString);
var resp = context.HttpContext.Response;
context.Logger.LogInformation($"redirect {req.Scheme}://{req.Host}{req.Path}?{req.QueryString} to {newUrl}");
resp.StatusCode = 301;
resp.Headers[HeaderNames.Location] = newUrl;
context.Result = RuleResult.EndResponse;
}
else if (this._behavior == RouteSubDomainBehavior.Rewrite)
{
var host = req.Host.Value;
var userStr = req.Host.Value.Substring(0, host.Length - this._domainWithPort.Length - 1);
req.Host= new HostString(this._domainWithPort);
var oldPath = req.Path;
req.Path = $"/{userStr}{oldPath}";
context.Logger.LogInformation($"rewrite {oldPath} as {req.Path}");
context.Result = RuleResult.SkipRemainingRules;
}
else{
throw new Exception($"unknow SubDomainBehavoir={this._behavior}");
}
}
}
(Note I use Rewrite here. If you like, feel free to change it to RouteSubDomainBehavior.Redirect.)
And then invoke the rewriter middleware just after app.UseStaticFiles():
app.UseStaticFiles();
// note : the invocation order matters!
app.UseRewriter(new RewriteOptions().Add(new RouteSubDomainRule("domain.com:5001",RouteSubDomainBehavior.Rewrite)));
app.UseMvc(...)
By this way,
user1.domain.com:5001/ will be rewritten as (or redirected to) domain.com:5001/user1
user1.domain.com:5001/Index will be rewritten as(or redirected to) domain.com:5001/user1/Index
user1.domain.com:5001/Home/Index will be rewritten as (or redirected to) domain.com:5001/user1//HomeIndex
static files like user1.domain.com:5001/lib/jquery/dist/jquery.min.js won't be rewritten/redirected because they're served by UseStaticFiles.
Another Approach Using IModelBinder
Although you can route it by rewritting/redirecting as above, I suspect what your real needs are binding parameters from Request.Host. If that's the case, I would suggest you should use IModelBinder instead. For example, create a new [FromHost] BindingSource:
internal class FromHostAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource( "FromHostBindingSource", "From Host Binding Source", true, true);
public BindingSource BindingSource {get{ return FromHostAttribute.Instance; }}
}
public class MyFromHostModelBinder : IModelBinder
{
private readonly string _domainWithPort;
public MyFromHostModelBinder()
{
this._domainWithPort = "domain.com:5001"; // in real project, use by Configuration/Options
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var req = bindingContext.HttpContext.Request;
var host = req.Host.Value;
var name = bindingContext.FieldName;
var userStr = req.Host.Value.Substring(0, host.Length - this._domainWithPort.Length - 1);
if (userStr == null) {
bindingContext.ModelState.AddModelError(name, $"cannot get {name} from Host Domain");
} else {
var result = Convert.ChangeType(userStr, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(result);
}
return Task.CompletedTask;
}
}
public class FromHostBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var has = context.BindingInfo?.BindingSource == FromHostAttribute.Instance;
if(has){
return new BinderTypeModelBinder(typeof(MyFromHostModelBinder));
}
return null;
}
}
Finally, insert this FromHostBinderProvider in your MVC binder providers.
services.AddMvc(otps =>{
otps.ModelBinderProviders.Insert(0, new FromHostBinderProvider());
});
Now you can get the user1.domain.com automatically by:
public IActionResult Index([FromHost] string username)
{
...
return View(view_model_by_username);
}
public IActionResult Edit([FromHost] string username, string id)
{
...
return View(view_model_by_username);
}
The problem after login the Identity cookie not shared in sub-domain
Here my Code where's wrong !!!
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public static Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder dataProtectionBuilder;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("ConnectionDb")));
services.AddIdentity<ExtendIdentityUser, IdentityRole>(options =>
{
options.Password.RequiredLength = 8;
options.Password.RequireUppercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredUniqueChars = 0;
options.Password.RequireLowercase = false;
}).AddEntityFrameworkStores<ApplicationDbContext>(); // .AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options => options.CookieManager = new CookieManager());
services.AddHttpContextAccessor();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IExtendIdentityUser, ExtendIdentityUserRepository>();
services.AddScoped<IItems, ItemsRepository>();
services.AddMvc(otps =>
{
otps.ModelBinderProviders.Insert(0, new FromHostBinderProvider());
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
//app.UseHttpsRedirection();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
And this class to sub-domain like that https://user1.localhost:44390/Home/Index
internal class FromHostAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromHostBindingSource", "From Host Binding Source", true, true);
public BindingSource BindingSource { get { return FromHostAttribute.Instance; } }
}
public class MyFromHostModelBinder : IModelBinder
{
private readonly string _domainWithPort;
public MyFromHostModelBinder()
{
this._domainWithPort = "localhost:44390"; // in real project, use by Configuration/Options
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var req = bindingContext.HttpContext.Request;
var host = req.Host.Value;
var name = bindingContext.FieldName;
var userStr = req.Host.Value.Substring(0, host.Length - this._domainWithPort.Length);
if (string.IsNullOrEmpty(userStr))
{
bindingContext.ModelState.AddModelError(name, $"cannot get {name} from Host Domain");
}
else
{
var result = Convert.ChangeType(userStr, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(result);
}
return Task.CompletedTask;
}
}
public class FromHostBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var has = context.BindingInfo?.BindingSource == FromHostAttribute.Instance;
if (has)
{
return new BinderTypeModelBinder(typeof(MyFromHostModelBinder));
}
return null;
}
}
Using ICookieManager
public class CookieManager : ICookieManager
{
#region Private Members
private readonly ICookieManager ConcreteManager;
#endregion
#region Prvate Methods
private string RemoveSubdomain(string host)
{
var splitHostname = host.Split('.');
//if not localhost
if (splitHostname.Length > 1)
{
return string.Join(".", splitHostname.Skip(1));
}
else
{
return host;
}
}
#endregion
#region Public Methods
public CookieManager()
{
ConcreteManager = new ChunkingCookieManager();
}
public void AppendResponseCookie(HttpContext context, string key, string value, CookieOptions options)
{
options.Domain = RemoveSubdomain(context.Request.Host.Host); //Set the Cookie Domain using the request from host
ConcreteManager.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(HttpContext context, string key, CookieOptions options)
{
ConcreteManager.DeleteCookie(context, key, options);
}
public string GetRequestCookie(HttpContext context, string key)
{
return ConcreteManager.GetRequestCookie(context, key);
}
#endregion
}

asp.net-core least steps to create and save new entry from API

I want to take a few post query parameters from an API i have and create a new entry. I wanted to do this with in the method with out needing to load context or something.
namespace fais.printing_services.Controllers
{
[Produces("application/json")]
[Route("api/[controller]/[action]")]
public class printController : Controller
{
private readonly IHostingEnvironment _appEnvironment;
public printController(IHostingEnvironment appEnvironment)
{
_appEnvironment = appEnvironment;
}
/**/
[HttpPost]
public IActionResult request(string id="test_default", string url = "", string html = "")
{
print_job _print_job = new print_job();
_print_job.html = html;
_print_job.options = options; //json object string
_print_job.url = url;
using (ApplicationDbContext db = new ApplicationDbContext())
{
db.print_job.Add(_print_job);
db.SaveChanges();
}
return Json(new
{
save = true
});
}
}
}
I just want to be able create a new print_job entry and save it when the API is called and return a json response.
Add ApplicationDbContext to controller constructor, it will be injected automatically (if your Startup.cs is like recommeneded):
private readonly IHostingEnvironment _appEnvironment;
private readonly ApplicationDbContext _db;
public printController(IHostingEnvironment appEnvironment, ApplicationDbContext db)
{
_appEnvironment = appEnvironment;
_db = db;
}
[HttpPost]
public IActionResult request(string id="test_default", string url = "", string html = "")
{
var _print_job = new print_job()
{
html = html,
options = options,
url = url,
}
_db.print_job.Add(_print_job);
_db.SaveChanges();
return Json(new { save = true });
}

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.