How to: Parameter binding from multiple sources - c#

Currently I'm trying to create a web api based on asp.net core 2.0 and I'd like to create a nested route. In case of a put request it sends a part of the information in the route and another part in the body.
Requirements
The desired url to call would be
https://localhost/api/v1/master/42/details
If we'd like to create a new detail below our master 42 I would expect to send the data of the details in the body while the id of the master comes out of the route.
curl -X POST --header 'Content-Type: application/json' \
--header 'Accept: application/json' \
-d '{ \
"name": "My detail name", \
"description": "Just some kind of information" \
}' 'https://localhost/api/v1/master/42/details'
The outcoming response of the request would be
{
"name": "My detail name",
"description": "Just some kind of information",
"masterId": 42,
"id": 47
}
and a location url within the response header like
{
"location": "https://localhost/api/v1/master/42/details/47
}
Work done so far
To get this to work I created this controller:
[Produces("application/json")]
[Route("api/v1/master/{masterId:int}/details")]
public class MasterController : Controller
{
[HttpPost]
[Produces(typeof(DetailsResponse))]
public async Task<IActionResult> Post([FromBody, FromRoute]DetailCreateRequest request)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);
var response = await Do.Process(request);
return CreatedAtAction(nameof(Get), new { id = response.Id }, response);
}
}
Which uses these classes:
public class DetailCreateRequest
{
public int MasterId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class DetailResponse
{
public int Id { get; set; }
public int MasterId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
The problem
So far most of the stuff works as expected. The only thing that really doesn't work is merging the MasterId from the route into the DetailCreateRequest that comes from the body.
First try: Use two attributes on the parameter
I tried to combine these two things by this action call:
public async Task<IActionResult> Post([FromBody, FromRoute]DetailCreateRequest request)
But the incoming object only had a MasterId of zero. If I change the order of the two attributes, then only the id from the route will be taken and all values within the body are ignored (so seems to be first attribute wins).
Second try: Use two different parameters in action
Another approach that I tried was this action call:
public async Task<IActionResult> Post([FromRoute]int masterId, [FromBody]DetailCreateRequest request)
In the first spot this looks okay, cause now I have both values within the controller action. But my big problem with this approach is the model validation. As you can see in the above code I check ModelState.IsValid which was filled through some checks from FluentValidation, but these checks can't be really done, cause the object wasn't build up correctly due to the missing master id.
(Not-working) Idea: Create own attribute with merge parameters
Tried to implement something like this:
public async Task<IActionResult> Post([FromMultiple(Merge.FromBody, Merge.FromRoute)]DetailCreateRequest request)
If we already would have something like this, that would be great. The order of the arguments within the attribute would give out the order in which the merge (and possible overwrites) would happen.
I already started with implementing this attribute and creating the skeleton for the needed IValueProvider and IValueProviderFactory. But it seems to be a quite lot of work. Especially finding all the nifty details to make this work seamlessly with the whole pipeline of asp.net core and other libraries I'm using (like swagger through swashbuckle).
So my question would be, if there already exists some mechanism within asp.net core to achieve such a merge or if anybody is aware about an already existing solution or about a good example on how to implement such a beast.
Solution so far: Custom ModelBinder
After getting the answer from Merchezatter I look into how to create a custom model binder and came up with this implementation:
public class MergeBodyAndValueProviderBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var body = bindingContext.HttpContext.Request.Body;
var type = bindingContext.ModelMetadata.ModelType;
var instance = TryCreateInstanceFromBody(body, type, out bool instanceChanged);
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var setters = type.GetProperties(bindingFlags).Where(property => property.CanWrite);
foreach (var setter in setters)
{
var result = bindingContext.ValueProvider.GetValue(setter.Name);
if (result != ValueProviderResult.None)
{
try
{
var value = Convert.ChangeType(result.FirstValue, setter.PropertyType);
setter.SetMethod.Invoke(instance, new[] { value });
instanceChanged = true;
}
catch
{ }
}
}
if (instanceChanged)
bindingContext.Result = ModelBindingResult.Success(instance);
return Task.CompletedTask;
}
private static object TryCreateInstanceFromBody(Stream body, Type type, out bool instanceChanged)
{
try
{
using (var reader = new StreamReader(body, Encoding.UTF8, false, 1024, true))
{
var data = reader.ReadToEnd();
var instance = JsonConvert.DeserializeObject(data, type);
instanceChanged = true;
return instance;
}
}
catch
{
instanceChanged = false;
return Activator.CreateInstance(type);
}
}
}
It tries to deserialize the body into the desired object type and afterwards tries to apply further values from the available value providers. To get this model binder to work I had to decorate the destination class with the ModelBinderAttribute and made the MasterId internal, so that swagger doesn't announce it and JsonConvert doesn't deserialize it:
[ModelBinder(BinderType = typeof(MergeBodyAndValueProviderBinder))]
public class DetailCreateRequest
{
internal int MasterId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Within my controller the action method parameters are still containing the [FromBody] flag, cause it is used by swagger to announce on how the method can be called, but it never will be called, cause my model binder has a higher priority.
public async Task<IActionResult> Post([FromBody]DetailCreateRequest request)
So it is not perfect, but works good so far.

That is looks like a right choice:
[HttpPost]
[Produces(typeof(DetailsResponse))]
public async Task<IActionResult> Post([FromRoute]int masterId, [FromBody]DetailCreateRequest request) {
//...
}
But if you have some problems with domain model validation, create custom Dto object without master Id.
Otherwise you can use custom model binder, and then work with arguments from action and binding contexts.

I'm not sure if this works in Asp.Net-Core 2.0, but we use the following in 3.1 to have a single request object which gets its properties from multiple locations:
// Annotate the action parameter with all relevant attributes
public async Task<IActionResult> Post([FromBody][FromRoute][FromQuery]DetailCreateRequest request) { ... }
// Annotate each property separately, so the binder(s) don't overwrite
public class DetailCreateRequest
{
[FromRoute]
public int MasterId { get; set; }
[FromBody]
public string Name { get; set; }
[FromQuery]
public string Description { get; set; }
}

It works with .Net 6:
[HttpPost]
[Route("{id}")]
public async Task<ActionResult<CustomerResponse>> Post([FromRoute, FromBody] CustomerPostRequest request)
{
return Ok();
}
public class CustomerPostRequest
{
[FromRoute(Name = "id")]
public int Id { get; set; }
[FromBody]
public string Name { get; set; }
}
Set the your required "source" attributes on the single request object parameter, and inside this object add each property the relevant "source" attribute.
Make sure the FromBody is the last one (it didn't work when I switched them).

Related

.NET Core API - Updating all fields while updating with DTO object

I created a .NET Core API project as below. Everything works very well. But when I send a small JSON file, the null fields in the DTO are reflected in the database. So how can I update only the submitted fields?
When I send only the name field in the JSON object, it updates all the other fields, how can I do this in SaveChangesAsync in the DataContext?
When I send only the name field in the JSON object, it records all the fields as null. How can I prevent this? In other words, only the sent data should be updated, and the others should be recorded with their original values. Is there any way I can achieve this within the dbcontext object?
I am sending a JSON like here, but because the description field is empty inside the JSON object, it is changed to null in the database.
{
"id": 2,
"name": "test"
}
CompanyController, I am sending the JSON object via the body:
[HttpPut]
public async Task<IActionResult> Update([FromBody] CompanyUpdateDto updateCompany)
{
await _service.UpdateAsync(_mapper.Map<Company>(updateCompany));
return CreateActionResult(CustomResponseDto<CompanyUpdateDto>.Success(204));
}
I am sending my updatedDto object, sometimes name, and description fields, sometimes just the name field.
public class CompanyUpdateDto
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public DateTime? UpdatedDate { get; set; }
}
CompanyModel:
public class Company
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public DateTime? CreatedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
}
DataContext:
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
foreach (var item in ChangeTracker.Entries())
{
if (item.Entity is BaseEntity entityReference)
{
switch (item.State)
{
case EntityState.Added:
{
entityReference.CreatedDate = DateTime.UtcNow;
break;
}
case EntityState.Modified:
{
Entry(entityReference).Property(x => x.CreatedDate).IsModified = false;
break;
}
}
}
}
return base.SaveChangesAsync(cancellationToken);
}
With AutoMapper, you can define a rule that only map from the source member to the destination member if the source member is not null via .Condition().
You may refer to the example in here.
CreateMap<CompanyUpdateDto, Company>()
.ForAllMembers(opt => opt.Condition((src, dest, value) => value != null));
Demo # .NET Fiddle
A concern is that you need to fetch the existing entity and map it with the received object to be updated as below:
[HttpPut]
public async Task<IActionResult> Update([FromBody] CompanyUpdateDto updateCompany)
{
// Get existing entity by id (Example)
var _company = await _service.GetAsync(updateCompany.Id);
// Map source to destination
_mapper.Map<CompanyUpdateDto, Company>(updateCompany, _company);
await _service.UpdateAsync(_company);
return CreateActionResult(CustomResponseDto<CompanyUpdateDto>.Success(204));
}
You can also ignore null values during serialization:
var company = new CompanyUpdateDto();
company.Description = "New description";
JsonSerializerOptions options = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
var serialized = JsonSerializer.Serialize(company,options);
You will have to make design decisions here for your API update operation.
Get and Put the objects in full.
When retrieving an object, your Get operation must return the object in full detail. Then, when any fields change, the client will send the object back in full to the Put endpoint. In this way, you will keep the values for all fields.
However, in some cases, you only want to expose a subset of the fields and leave some of the fields untouched or updated by the system. In those cases, you will have to retrieve the object from the database by some identifier and then assign the fields from the incoming object.
Use JSON Patch
You will have a Patch endpoint for the resource. In the request body, you specify what operation for the object and which field has changed. When receiving the request, you will apply the changes based on the operation and fields in the request body.
The downside for the second option is that your client must follow the JSON Patch standards.

Unify JSON of different formats - C# WebApi

Short version
How to create API that can be shared between different parties that use different conventions for JSON properties in both places, request and response. In other words, if client #1 sends JSON request in snake case, it should receive JSON response in snake case. If client #2 sends camel case, they should receive camel case back in the response.
Long version with explanation and examples
1. snake_case { some_id: 1, some_name: "www" }
2. alllowercase { someid: 2, somename: "www" }
3. TitleCase { SomeId: 1, SomeName: "www" }
4. camelCase { someId: 1, someName: "www" }
Default model binder is case insensitive, and can handle options 2, 3, 4 naturally, but if input JSON contains "snake_case", default model binder will not extract it [FromUri] and will not map it to a model.
class SomeModel
{
int SomeId { get; set; }
string SomeName { get; set; }
}
A controller.
class SomeController()
{
[HttpGet]
public dynamic SomeGetAction([FromUri] SomeModel model) { ... }
[HttpPost]
public dynamic SomePostAction([FromBody] SomeModel model) { ... }
}
Approach #1: Attributes
In this case, we tell the controller to search for snake case properties in an incoming JSON. If the snake case attribute won't be found, it should try to extract properties without underscore.
class SomeModel
{
[JsonProperty(Name="some_id")]
int SomeId { get; set; }
[JsonProperty(Name="some_name")]
string SomeName { get; set; }
}
Approach #2: Get-Set wrappers
We can create both kinds of property in our model to cover all possible JSON variations.
class SomeModel
{
int some_id { get; set; }
string some_name { get; set; }
int SomeId { get { return some_id; } set { some_id = value; } }
string SomeName { get { return some_name; } set { some_name = value; } }
}
Approach #3: Custom model binder
In this case, create an intermediate layer that can parse request header and body and extract properties of any case.
class MyModelBinder<SomeModel>()
{
public bool BindModel(HttpActionContext actionCtx, ModelBindingContext modelCtx)
{
var queryGet = actionCtx
.Request
.GetQueryNameValuePairs()
.ToDictionary(o => o.Key, o => o.Value as object);
var queryPost = actionCtx
.Request
.Content
.ReadAsAsync<dynamic>
.Result
.ToObject<Dictionary<string, object>>);
modelContext.model.SomeId = // try to find property in the request
queryGet["some_id"] ??
queryGet["someid"] ??
queryGet["someId"] ??
queryGet["SomeId"];
return true;
}
}
class SomeController()
{
[HttpGet]
public dynamic SomeGetAction([MyModelBinder<typeof(SomeModel)>] SomeModel model) { ... }
[HttpPost]
public dynamic SomePostAction([MyModelBinder<typeof(SomeModel)>] SomeModel model) { ... }
}
Approach #4: Contract resolver
Approaches #1 and #2 seem too verbose and is not accepted by some team members. Approach #3 seems to cover all cases, but for some reason, it hides all model properties from Swagger, so we decided to take a look at a contract resolver that can be set up on the controller level and modify incoming and outgoing JSON before or after the action. We tried this approach https://stackoverflow.com/a/52766426/437393 and it works fine for the outgoing JSON in the response, but it doesn't transform incoming JSON in the request.
So, the alternate question is, how to make sure that the controller will receive JSON of a specific format using Contract Resolver, always convert JSON to snake case?

Handle dot (.) character in parameter name in post request

I'm building a restful server to handle post requests. However, there is a dot (.) in one of the parameter names which I don't know how to handle since C# does not allow dot (.) in their variable names. The parameter name is "data.json" without the quotation.
I read some posts about C# converting dots (.) into underscores (_), so I tried to name the variable "data_json", which doesn't work, the string is empty.
Object Class:
public class Lead {
public string data_json { get; set; }
public string page_id { get; set; }
public string page_url { get; set; }
}
Post Handler:
public HttpResponseMessage Post(Lead value) {
try {
Log.CreatePostLog(page_id + value.data_json);
} catch (Exception e) {
return Request.CreateResponse(HttpStatusCode.BadRequest, e.Message);
}
return Request.CreateResponse(HttpStatusCode.OK, "Done!");
}
Post Request Body (Cannot be changed):
page_url=http://ramdomurl/
&page_id=123456
&data.json={"time_submitted":["04:34 PM UTC"],"full_name":["John Doe"]}
When the request is made, the log shows page_id but nothing after.
It should show page_id and the Json string after it.
One possible solution is to create a custom model binder, which handles fields whose names contain the "." character, and apply this binder to the model class.
The code of the binder:
// this binder assigns form fields with dots to properties with underscores:
// e.g. data.json -> data_json
public class Dot2UnderscoreModelBinder : IModelBinder
{
// for regular fields, we will use the default binder
private readonly DefaultModelBinder _default = new DefaultModelBinder();
public object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
// handle the regular fields
var model = _default.BindModel(controllerContext, bindingContext);
// handle the special fields
if (model != null)
{
var modelType = model.GetType();
var form = controllerContext.HttpContext.Request.Form;
foreach (var key in form.AllKeys)
{
if (key.Contains(".")) // special field
{
// model property must be named by the convention "." -> "_"
var propertyName = key.Replace(".", "_");
var propertyInfo = modelType.GetProperty(propertyName);
propertyInfo?.SetValue(model, form[key]);
}
}
}
return model;
}
}
Note that this is a simplistic implementation, it only supports string properties, and its performance is not optimal. But it is a working starting point.
Now you need to apply the above binder to the model class:
[ModelBinder(typeof(Dot2UnderscoreModelBinder))]
public class Lead
{
//... properties
}
It worth noting that the controller must derive from Controller in System.Web.Mvc namespace, and not ApiController in System.Web.Http, because that latter doesn't trigger model binders:
using System.Web.Mvc;
....
public class MyController : Controller
{
[HttpPost]
public ActionResult Post(Lead value)
{
//... do some stuff
return base.Content("Done!");
}
}
ASP.NET Core
Just as a side note, in ASP.NET Core the same can be achieved in a very simple way, by applying FromForm attribute:
public class Lead
{
[FromForm(Name = "data.json")] // apply this attribute
public string data_json { get; set; }
//... other properties
}
Use NewtonsoftJson PropertyName attribute:
public class Lead
{
[JsonProperty(PropertyName = "data.json")]
public string data_json { get; set; }
public string page_id { get; set; }
public string page_url { get; set; }
}
Add nuget package:
https://www.nuget.org/packages/Newtonsoft.Json/

Postman Form Data to ASP.NET Core 2.2 Controller not binding to Command with private setters

Tools
Visual Studio 2017
ASP.NET Core 2.2
Postman v7.2.0
What I'm trying to do
Send FormData from Postman to an ASP.NET Core controller and have the data from the request bind to to a command class that has properties with private setters.
I've sent JSON data using the same setup (private setters) with no problem. The FromBody attribute deserialises the JSON string to the model without errors.
The Problem
Properties that are primitive types do not bind if the model has a private setter. However, complex types do regardless of the access modifier.
Controller
[HttpPost]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> CreateItemAsync([FromForm]CreateItemCommand command)
{
bool result = false;
commandResult = await _mediator.Send(command);
if (!commandResult)
{
return BadRequest();
}
return Ok();
}
Command
Note: The Title property has been left with a public setter deliberately to illustrate the behviour
[DataContract]
public class CreateItemCommand
:IRequest<bool>
{
[DataMember]
public string Title { get; set; }
[DataMember]
public string Description { get; private set; }
[DataMember]
public int Count { get; private set; }
[DataMember]
public HashSet<string> Tags { get; private set; }
[DataMember]
public string ItemDate { get; private set; }
[DataMember]
public List<IFormFile> Documents { get; private set; }
public CreateItemCommand()
{
Skills = new HashSet<string>();
Systems = new HashSet<string>();
}
public CreateItemCommand(string title, string description,
int count, HashSet<string> tags, string itemDate,
List<IFormFile> documents)
: this()
{
Title = title;
Description = description;
Count = count
Tags = tags;
ItemDate = itemDate;
Documents = documents;
}
}
In Postman I now setup the request as follows:
I've had to obfuscate some of the information, but you can see that the primitive types with private setters are not set.
Questions
Why does the property access modifier only affect properties with primitive types?
Why does this happens when the parameter attribute is set to FromForm but not when it's set to FromBody
Why does the property access modifier only affect properties with primitive types?
For Asp.Net Core ModelBinder, it will check whether the property is private access setter by ComplexTypeModelBinder code below:
protected virtual object CreateModel(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// If model creator throws an exception, we want to propagate it back up the call stack, since the
// application developer should know that this was an invalid type to try to bind to.
if (_modelCreator == null)
{
// The following check causes the ComplexTypeModelBinder to NOT participate in binding structs as
// reflection does not provide information about the implicit parameterless constructor for a struct.
// This binder would eventually fail to construct an instance of the struct as the Linq's NewExpression
// compile fails to construct it.
var modelTypeInfo = bindingContext.ModelType.GetTypeInfo();
if (modelTypeInfo.IsAbstract || modelTypeInfo.GetConstructor(Type.EmptyTypes) == null)
{
var metadata = bindingContext.ModelMetadata;
switch (metadata.MetadataKind)
{
case ModelMetadataKind.Parameter:
throw new InvalidOperationException(
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForParameter(
modelTypeInfo.FullName,
metadata.ParameterName));
case ModelMetadataKind.Property:
throw new InvalidOperationException(
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForProperty(
modelTypeInfo.FullName,
metadata.PropertyName,
bindingContext.ModelMetadata.ContainerType.FullName));
case ModelMetadataKind.Type:
throw new InvalidOperationException(
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForType(
modelTypeInfo.FullName));
}
}
_modelCreator = Expression
.Lambda<Func<object>>(Expression.New(bindingContext.ModelType))
.Compile();
}
return _modelCreator();
}
Why does this happens when the parameter attribute is set to FromForm but not when it's set to FromBody
For FromBody, it is used JsonInputFormatter to bind the model from the body request, it's used JsonConvert.DeserializeObject to deserilize the object and Newtonsoft.Json support deserize the object which contains private setter from json string.

complex object is null in Web API method using Httpclient

I have Web API service deployed and and consuming in another web application. Web API method take complex object (List object) and results also complex object.
So I created local models for Input parameter and results model to match with Web API complex objects in web application. then I passed JsonConvert.SerializeObject for that parameter. But when I debug in Web API that parameter value showing null.
Web application
[Serializable]
public class PreferencesInput
{
public string ShortName { get; set; }
public string ShortNameDescription { get; set; }
.....
}
[Serializable]
public class PreferencesOuput
{
public bool Status { get; set; }
public string Error { get; set; }
}
public class HomeController : Controller
{
public ActionResult Index()
{
RunAsync().Wait();
return View();
}
private static async Task RunAsync()
{
var inputs = new List<PreferencesInput>();
var input = new PreferencesInput
{
ShortName = "REGION",
ShortNameDescription = "Geographical regions",
OptedInFlag = true
};
inputs.Add(input);
....
...
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:8585/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
HttpResponseMessage response = await client.GetAsync("preferences/updatepreferences/?id='3016523'
&optInInterestAreas=" + JsonConvert.SerializeObject(inputs) +
"&solicitationFlag=false").ConfigureAwait(false);;
if (response.IsSuccessStatusCode)
{
string results = await response.Content.ReadAsStringAsync();
var myList = JsonConvert.DeserializeObject<List<PreferencesOuput>>(results);
}
web API
[Route("preferences/updatepreferences")]
[HttpGet]
public PreferencesOuput UpdatePreferences(string id, IEnumerable<PreferencesInput> optInInterestAreas, bool solicitationFlag)
{
.....
}
Only difference is Web application Input model has less parameters than the Web API model.
What I am doing wrong here?
IEnumerable<PreferencesInput> optInInterestAreas is null
update
I can see serialization date like below before sending to Web API call, In Web API method it is showing null, rest of the parameters are showing correct.
[{"ShortName":"REGION","ShortNameDescription":"Geographical regions","ShortSubName":null,"Description":null,"OptedInFlag":true},
{"ShortName":"REGION","ShortNameDescription":"Asia Pacific","ShortSubName":"ASIA_PACIFIC","Description":null,"OptedInFlag":true},
{"ShortName":"REGION","ShortNameDescription":"Canada","ShortSubName":"CANADA","Description":null,"OptedInFlag":true}]
You could try to specify the route with parameters. Something like:
[Route("preferences/updatepreferences/{id}/{optInInterestAreas}/{solicitationFlag:bool}")]
Your optInInterestAreas parameter is null because in Web API, the parameter binding rules specify that anything other than a "simple" parameter type (string, int, etc) is assumed to be passed in the body, not the route or query string as you're doing. You could get this to work by using the [FromUri] attribute on that parameter or by defining a custom type converter, but I would highly recommend changing your API as it does not follow generally accepted best practices.
By convention, GET is assumed to be side-effect-free, but I'm guessing something called UpdatePreferences almost certainly changes data. I would consider using a different verb and passing the updated preferences in the body. POST is better, but if you want it to be truly RESTful, you should ensure that the URI uniquely identifies the resource and use PUT.
I would start by changing your input model to something like this:
public class PreferencesInput
{
public IList<InterestArea> InterestAreas { get; set; }
public bool SolicitationFlag { get; set; }
}
public class InterestArea
{
public string ShortName { get; set; }
public string ShortNameDescription { get; set; }
...
}
Then define your API action like this:
[Route("preferences/{id}")]
[HttpPut]
public PreferencesOuput UpdatePreferences(string id, PreferencesInput preferences)
{
...
}
As you can see, the URI now uniquely identifies the thing, and the verb specifies what you want to "do"; in this case, completely replace whatever is at that URI (if anything) with the thing you are passing.
Side-note:
On the MVC side, calling Wait() in your Index action is blocking a thread while waiting for your async method to complete. That's a serious invitation for deadlocks. Async only works properly if you go "all the way" with it. In this case it's incredibly easy - just change the Index action to:
public async Task<ActionResult> Index()
{
await RunAsync();
return View();
}

Categories