Asp.net FromBody fails when payload is null - asp.net-core-mvc-2.0

I have a controller that expects to get a json payload ie
public async Task<IActionResult> InitUser([FromBody] Tenant tenant)
This is fine when a valid json payload is sent, but if no payload is sent I get the error
No input formatter was found to support the content type 'null' for use with the [FromBody] attribute
And HTTP status code 415 is returned to the client.
Is it possible to catch this case and set the json payload to some default value so that the input formatter wont throw this error?

You can remove the [FromBody] attribute and get the body directly from the HTTP request. Make sure you have the [HttpPost] Attribute decoration.
In the example below you can see how to do that in a very simple way. You can also create your own CustomAttribute and middleware if you want to make it a system wide and elegant solution.
You will also need to parse the body. For that you can use JsonConverter if you like.
[HttpPost]
public async Task<IActionResult> Index()
{
Tenant tenant;
string result;
using (var reader = new StreamReader(Request.Body))
{
var body = await reader.ReadToEndAsync();
result = body;
}
//Define the naming strategy here if you need
DefaultContractResolver contractResolver = new DefaultContractResolver
{
//NamingStrategy = new SnakeCaseNamingStrategy()
NamingStrategy = new CamelCaseNamingStrategy()
};
//Optional configuration to add in DeserializeObject constructor as second param.
var jsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = contractResolver,
};
tenant = JsonConvert.DeserializeObject<Tenant>(result);
Console.WriteLine(tenant);
return View();
}

Related

MVC optional body parameter

I am trying to wire up a webhook from a 3rd party system.
When creating the subscription it hits the URL i provide and requires a validated token returned to create the hook.
When the event is triggered the hook posts to the same URL i provided with data in the body.
How can I get a Core 2.1 MVC controller/routing to see these as either two different methods on the controller or a method signature where the complex object is optional?
Either two POST methods (this creates ambiguity exception)
public async Task<IActionResult> Index(){}
public async Task<IActionResult> Index([FromBody] ComplexObject co){}
or complexObject is optional (if not it throws a Executing ObjectResult, writing value of type '"Microsoft.AspNetCore.Mvc.SerializableError" on the subscription creation step.)
public async Task<IActionResult> Index([FromBody] ComplexObject co){}
Another way around this :
public class AllowBindToNullAttribute : ModelBinderAttribute
{
public AllowBindToNullAttribute()
: base(typeof(AllowBindToNullBinder))
{
}
public class AllowBindToNullBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var stream = bindingContext.HttpContext.Request.Body;
string body;
using (var reader = new StreamReader(stream))
{
body = await reader.ReadToEndAsync();
}
var instance = JsonConvert.DeserializeObject(body, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(instance);
}
}
}
You'd use it like this:
public async Task<IActionResult> Index(
[FromBody] [AllowBindToNull] ComplexObject co = null){}
I used the empty parameter method signature and checked the body for data. Not ideal.

How to add another parameter to formdata in AngularJS

I am sending a file object to my server from angularJS like this -
var fd = new FormData();
fd.append('file', file);
var deferred = $q.defer();
$http.post(uploadUrl, fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).success(function(data, status, headers, config){
deferred.resolve(data);
})
.error(function(data, status, headers, config){
deferred.reject('some error occurred');
});
This is my Asp.Net MVC controller -
public HttpResponseMessage Post([FromBody]HttpPostedFileBase file)
I want to send one more parameter (say userId) with the same request. Is it possible to do so? How should I recieve it at the server end. I tried adding fd.append('userId', userId) to the formdata object, but it didn't work. Please tell how to achieve this.
Thanks in advance.
Edit1: When I do bind like I mention, I get below error:
Can't bind multiple parameters ('file' and 'userId') to the request'scontent.
Edit2: I also tried creating an object like so -
public class PostedInfo{
public HttpPostedFileBase file {get;set;}
public string userId {get;set;}
}
and changed post method to -
public HttpResponseMessage Post([FromBody]PostedInfo fd)
But, now this error is thrown, which is quite obvious as the request is json -
The request entity's media type 'multipart/form-data' is not supported for this resource.
Not sure how to tell tell that fd.file is a multipart/form-data entity.
I finally figured this out. The issue is that HttpPostedFileBase seems to not work quite well with webapi, so instead one should use MultipartFormDataStreamProvider for this. Code below -
[HttpPost]
public async Task<HttpResponseMessage> Post()
{
if (Request.Content.IsMimeMultipartContent())
{
string path = HttpContext.Current.Server.MapPath("~/Resources");
var provider = new MultipartFormDataStreamProvider(path);
await Request.Content.ReadAsMultipartAsync(provider);
// get the additional parameter
var userId = provider.FormData.GetValues("userId").FirstOrDefault();
string filepath = provider.FileData[0].LocalFileName;
return <your_custom_response_here>;
}
Here, you can see, I also got the userId in the server code apart from uploading the file.
Well, MultipartFormDataStreamProvider will save the file with a cryptic name (BodyPart_...). You can get rid of that by building your own CustomMultipartFormDataStreamProvider. Found an awesome link as to how to format the file name while saving. Worth a read.

Render partial view as string

I know this looks like a duplicate question, but please read the whole question before marking it as duplicate.
First of all, I'm simulating the windows service in my ASP web application to send weekly emails, so in Global.asax I'm running my function that will send the emails.
Now the emails content is in HTML and I want to render the views to get the content. The problem is that in my function, I don't have any of the following :
Controller
ControllerContext
HttpContext
RoutData
... & much more. Which makes sense, because the function was invoked as a callback not as a HTTP request action.
I tried to use the RazorEngine to use the partial as a template by reading the file then using Razor.Parse() method. But I faced a lot of problems from this approach, because nothing is included in the template. What I mean is: it keeps telling me that The name "Html" does not exist in the current context OR 'CompiledRazorTemplates.Dynamic.becdccabecff' does not contain a definition for 'Html' even if I include the System.Web.Mvc.Html.
how can I solve this issue?.
I think the best approach is assuming you developed a real NT service and use HttpClient to send a http request to your partial view and receive the response as string and use it to make up your email. However, you can have HttpContext in RunScheduledTasks method by making some changes in Scheduler class.
public delegate void Callback();
to
public delegate void Callback(HttpContext httpContext);
add cache.Current_HttpContext = HttpContext.Current; to the Run method
public static void Run(string name, int minutes, Callback callbackMethod)
{
_numberOfMinutes = minutes;
CacheItem cache = new CacheItem();
cache.Name = name;
cache.Callback = callbackMethod;
cache.Cache = HttpRuntime.Cache;
cache.LastRun = DateTime.Now;
cache.Current_HttpContext = HttpContext.Current;
AddCacheObject(cache);
}
change CacheCallback to
private static void CacheCallback(string key, object value, CacheItemRemovedReason reason)
{
CacheItem obj_cache = (CacheItem)value;
if (obj_cache.LastRun < DateTime.Now)
{
if (obj_cache.Callback != null)
{
obj_cache.Callback.Invoke(obj_cache.Current_HttpContext);
}
obj_cache.LastRun = DateTime.Now;
}
AddCacheObject(obj_cache);
}
Edited:
How to use HttpClient
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("http://localhost/controller/action/");
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Without using 3rd party library, one can use this method to generate string of view in Global.asax.cs file
public class EmptyController : Controller { }
public string GetView(string viewName)
{
//Create an instance of empty controller
Controller controller = new EmptyController();
//Create an instance of Controller Context
var ControllerContext = new ControllerContext(Request.RequestContext, controller);
//Create a string writer
using (var sw = new StringWriter())
{
//get the master layout
var master = Request.IsAuthenticated ? "_InternalLayout" : "_ExternalLayout";
//Get the view result using context controller, partial view and the master layout
var viewResult = ViewEngines.Engines.FindView(ControllerContext, viewName, master);
//Crete the view context using the controller context, viewResult's view, string writer and ViewData and TempData
var viewContext = new ViewContext(ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
//Render the view in the string writer
viewResult.View.Render(viewContext, sw);
//release the view
viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
//return the view stored in string writer as string
return sw.GetStringBuilder().ToString();
}
}

How do I include a model with a RedirectToAction?

In the RedirectToAction below, I'd like to pass a viewmodel. How do I pass the model to the redirect?
I set a breakpoint to check the values of model to verify the model is created correctly. It is correct but the resulting view does not contain the values found in the model properties.
//
// model created up here...
//
return RedirectToAction("actionName", "controllerName", model);
ASP.NET MVC 4 RC
RedirectToAction returns a 302 response to the client browser and thus the browser will make a new GET request to the url in the location header value of the response came to the browser.
If you are trying to pass a simple lean-flat view model to the second action method, you can use this overload of the RedirectToAction method.
protected internal RedirectToRouteResult RedirectToAction(
string actionName,
string controllerName,
object routeValues
)
The RedirectToAction will convert the object passed(routeValues) to a query string and append that to the url(generated from the first 2 parameters we passed) and will embed the resulting url in the location header of the response.
Let's assume your view model is like this
public class StoreVm
{
public int StoreId { get; set; }
public string Name { get; set; }
public string Code { set; get; }
}
And you in your first action method, you can pass an object of this to the RedirectToAction method like this
var m = new Store { StoreId =101, Name = "Kroger", Code = "KRO"};
return RedirectToAction("Details","Store", m);
This code will send a 302 response to the browser with location header value as
Store/Details?StoreId=101&Name=Kroger&Code=KRO
Assuming your Details action method's parameter is of type StoreVm, the querystring param values will be properly mapped to the properties of the parameter.
public ActionResult Details(StoreVm model)
{
// model.Name & model.Id will have values mapped from the request querystring
// to do : Return something.
}
The above will work for passing small flat-lean view model. But if you want to pass a complex object, you should try to follow the PRG pattern.
PRG Pattern
PRG stands for POST - REDIRECT - GET. With this approach, you will issue a redirect response with a unique id in the querystring, using which the second GET action method can query the resource again and return something to the view.
int newStoreId=101;
return RedirectToAction("Details", "Store", new { storeId=newStoreId} );
This will create the url Store/Details?storeId=101
and in your Details GET action, using the storeId passed in, you will get/build the StoreVm object from somewhere (from a service or querying the database etc)
public ActionResult Details(string storeId)
{
// from the storeId value, get the entity/object/resource
var store = yourRepo.GetStore(storeId);
if(store!=null)
{
// Map the the view model
var storeVm = new StoreVm { Id=storeId, Name=store.Name,Code=store.Code};
return View(storeVm);
}
return View("StoreNotFound"); // view to render when we get invalid store id
}
TempData
Following the PRG pattern is a better solution to handle this use case. But if you don't want to do that and really want to pass some complex data across Stateless HTTP requests, you may use some temporary storage mechanism like TempData
TempData["NewCustomer"] = model;
return RedirectToAction("Index", "Users");
And read it in your GET Action method again.
public ActionResult Index()
{
var model=TempData["NewCustomer"] as Customer
return View(model);
}
TempData uses Session object behind the scene to store the data. But once the data is read the data is terminated.
Rachel has written a nice blog post explaining when to use TempData /ViewData. Worth to read.
Using TempData to pass model data to a redirect request in Asp.Net Core
In Asp.Net core, you cannot pass complex types in TempData. You can pass simple types like string, int, Guid etc.
If you absolutely want to pass a complex type object via TempData, you have 2 options.
1) Serialize your object to a string and pass that.
Here is a sample using Json.NET to serialize the object to a string
var s = Newtonsoft.Json.JsonConvert.SerializeObject(createUserVm);
TempData["newuser"] = s;
return RedirectToAction("Index", "Users");
Now in your Index action method, read this value from the TempData and deserialize it to your CreateUserViewModel class object.
public IActionResult Index()
{
if (TempData["newuser"] is string s)
{
var newUser = JsonConvert.DeserializeObject<CreateUserViewModel>(s);
// use newUser object now as needed
}
// to do : return something
}
2) Set a dictionary of simple types to TempData
var d = new Dictionary<string, string>
{
["FullName"] = rvm.FullName,
["Email"] = rvm.Email;
};
TempData["MyModelDict"] = d;
return RedirectToAction("Index", "Users");
and read it later
public IActionResult Index()
{
if (TempData["MyModelDict"] is Dictionary<string,string> dict)
{
var name = dict["Name"];
var email = dict["Email"];
}
// to do : return something
}
Another way to do it is to store it in the session.
var s = JsonConvert.SerializeObject(myView);
HttpContext.Session.SetString("myView", s);
and to get it back
string s = HttpContext.Session.GetString("myView");
myView = JsonConvert.DeserializeObject<MyView>(s);

Accessing resources via Uri in Asp.net mvc

I am working on an ASP.NET MVC web application in which I have an object with a Uri property. The Uri contains a restful link to a resource in the following form:
/Repository/Dataset/5
The Dataset action of the Repository controller returns the contents of dataset 5 as Json.
How do I call this method from the Uri and interpret the response as Json from within the object?
Many thanks.
In server side action return JsonResult.
public ActionResult Dataset(int id)
{
// reository code
return Json(model);
}
client side call $.getJSON.
My opinion is that you should not call your controller from anywhere in code.In ASP.NET MVC Controller is there to accept request, take data and choose proper view to be returned back.
Maybe you should add method on repository that is returning already JSONized data, or introduce "Middle man" that can serialize data returned from repository so controller can call middle man to do the job. Then repository (or "Middle man") can be called from anywhere in code.
e.g.(used Json.NET for json serialization):
public class MiddleMan
{
IRepository repository
public MiddleMan(IRepository repository)
{
this.repository = repository;
}
public string GetJsonObjects(int id)
{
return JsonConvert.SerializeObject(repository.GetObject(id));
}
}
then controller (or anywhere in the code) can call this middle class:
public string Dataset(int id)
{
return new MiddleMan(repository).GetJsonObjects(id);
}
For the time being I'm going to implement a uri extension method something along these lines, creating a WebRequest object for the Uri.
public static string GetContent(this Uri uri)
{
var myRequest = (HttpWebRequest) WebRequest.Create(uri);
myRequest.Method = "GET";
WebResponse myResponse = myRequest.GetResponse();
var sr = new StreamReader(myResponse.GetResponseStream(), System.Text.Encoding.UTF8);
string result = sr.ReadToEnd();
sr.Close();
myResponse.Close();
return result;
}

Resources