I'm using a third party reporting engine (stimulsoft) that calls an action on a controller via POST. Inside of the form, many fields are sent for the mechanics of the third party. Inside of the action I need some parameters all my parameters are inside of the URL.
I want to be able to use the model binder inside of my action.
At the moment I'm getting each fields one by one using this methods
var queryString = HttpUtility.ParseQueryString(Request.UrlReferrer.Query);
var preparedBy = queryString["preparedBy"];
var preparedAt = (queryString["preparedAt"] != null) ? Convert.ToDateTime(queryString["preparedAt"]) : DateTime.Today;
I would prefer to use a model and binding using the UrlReferrer. I've created a UrlReferrerValueProvider to bind from the action. I've tried that, but I'm getting a NullReferenceException on binder.BindModel line
public class UrlReferrerValueProvider : NameValueCollectionValueProvider
{
public UrlReferrerValueProvider(ControllerContext controllerContext)
: base(HttpUtility.ParseQueryString(controllerContext.HttpContext.Request.UrlReferrer.Query), CultureInfo.InvariantCulture)
{
}
}
public ActionResultat GetReportSnapshot()
{
var bindingContext = new ModelBindingContext()
{
ValueProvider = new UrlReferrerValueProvider(ControllerContext),
ModelName = "MyReportModel",
FallbackToEmptyPrefix = true
};
var binder = new DefaultModelBinder();
var myReportModel = binder.BindModel(ControllerContext, bindingContext);
[...]
return new EmptyResult();
}
public class MyReportModel
{
public string PreparedBy {get;set;}
public DateTime PreparedAt {get;set;}
}
Edited based on comments.
public class MyReportModel
{
public string PreparedBy {get;set;}
public DateTime PreparedAt {get;set;}
}
public class UrlReferrerValueProvider : NameValueCollectionValueProvider
{
public UrlReferrerValueProvider(ControllerContext controllerContext)
: base(HttpUtility.ParseQueryString(controllerContext.HttpContext.Request.UrlReferrer.Query), CultureInfo.InvariantCulture)
{
}
}
public ActionResult GetReportSnapshot(MyReportModel model)
{
this.UpdateModel(model, new UrlReferrerValueProvider(ControllerContext));
return new EmptyResult();
}
Related
I'm building WebAPI & WebApp, both of them using ASP.NET Core 2.1
My Web App is trying to send post request to the Web API using ViewModel that contains IFormFile and other properties. I know I have to use MultipartFormDataContent to post IFormFile, but I don't know how to implement it with my ViewModel because my ViewModel has List of other model.
I already try to google some solutions, but I only found solutions with simple ViewModel without List like these :
https://stackoverflow.com/a/41511354/7906006
https://stackoverflow.com/a/55424886/7906006.
Is there any solution like
var multiContent = new MultipartFormDataContent();
var viewModelHttpContent= new StreamContent(viewModel);
MultiContent.Add(viewModelHttpContent, "viewModel");
var response = await client.PostAsJsonAsync("/some/url", multiContent);
so i don't have to add my property to MultipartFormDataContent one by one and post it as json.
Here's my Web App ViewModel
public class CreateDataViewModel
{
public string PrimaryKeyNumber{ get; set; }
public List<Currency> ListOfCurrency { get; set; }
public IList<DataDetail> dataDetails { get; set; }
[DataType(DataType.Upload)]
public IFormFile Attachment { get; set; }
//And other properties like Boolean, Datetime?, string
}
Here's my Web App controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(CreateDataViewModel viewModel)
{
//How to implement MultipartFormDataContent with my ViewModel in here ?
//My code below returns Could not create an instance of type Microsoft.AspNetCore.Http.IHeaderDictionary. Type is an interface or abstract class and cannot be instantiated. Path 'Attachment.Headers.Content-Disposition', line 1, position 723.
//It works fine if I don't upload a file
HttpResponseMessage res = await _client.PostAsJsonAsync<CreateDataViewModel>("api/data/create", viewModel);
var result = res.Content.ReadAsStringAsync().Result;
if (res.IsSuccessStatusCode)
{
TempData["FlashMessageSuccess"] = "Data have been submitted";
return RedirectToAction("Index", "Home"); ;
}
//Code for error checking
}
Here's my Web API controller that catches the post response using CreateDataViewModel as parameter.
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> Create(CreateDataViewModel viewModel)
{
//Code to validate then save the data
}
don't know how to implement it with my ViewModel because my ViewModel has List of other model
You can refer to following code snippet and implement a custom model binder to achieve your requirement.
var multipartContent = new MultipartFormDataContent();
multipartContent.Add(new StringContent(viewModel.PrimaryKeyNumber), "PrimaryKeyNumber");
multipartContent.Add(new StringContent(JsonConvert.SerializeObject(viewModel.ListOfCurrency)), "ListOfCurrency");
multipartContent.Add(new StringContent(JsonConvert.SerializeObject(viewModel.dataDetails)), "dataDetails");
multipartContent.Add(new StreamContent(viewModel.Attachment.OpenReadStream()), "Attachment", viewModel.Attachment.FileName);
var response = await client.PostAsync("url_here", multipartContent);
Implement a custom model binder to convert incoming request data
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// code logic here
// ...
// ...
// fetch the value of the argument by name
// and populate corresponding properties of your view model
var model = new CreateDataViewModel()
{
PrimaryKeyNumber = bindingContext.ValueProvider.GetValue("PrimaryKeyNumber").FirstOrDefault(),
ListOfCurrency = JsonConvert.DeserializeObject<List<Currency>>(bindingContext.ValueProvider.GetValue("ListOfCurrency").FirstOrDefault()),
dataDetails = JsonConvert.DeserializeObject<List<DataDetail>>(bindingContext.ValueProvider.GetValue("dataDetails").FirstOrDefault()),
Attachment = bindingContext.ActionContext.HttpContext.Request.Form.Files.FirstOrDefault()
};
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
Apply it on API action method
public async Task<IActionResult> Create([ModelBinder(BinderType = typeof(CustomModelBinder))]CreateDataViewModel viewModel)
Test Result
Below soltuion worked for me:-
var multiContent = new MultipartFormDataContent();
var viewModelHttpContent= new StreamContent(viewModel);
multiContent.Add(viewModelHttpContent, "viewModel");
multiContent.Add(new StreamContent(file.OpenReadStream()), "Attachment", file.FileName);
var request = new HttpRequestMessage(HttpMethod.Post, "/some/url") { Content = multiContent};
var response = await client.SendAsync(request);
At Api end:-
public async Task Upload([FromForm] CreateDataViewModel postRequest)
This question has been asked before on SO and elsewhere in the context of MVC3 and there are bits and bobs about it related to ASP.NET Core RC1 and RC2 but niot a single example that actually shows how to do it the right way in MVC 6.
There are the following classes
public abstract class BankAccountTransactionModel {
public long Id { get; set; }
public DateTime Date { get; set; }
public decimal Amount { get; set; }
public readonly string ModelType;
public BankAccountTransactionModel(string modelType) {
this.ModelType = modelType;
}
}
public class BankAccountTransactionModel1 : BankAccountTransactionModel{
public bool IsPending { get; set; }
public BankAccountTransactionModel1():
base(nameof(BankAccountTransactionModel1)) {}
}
public class BankAccountTransactionModel2 : BankAccountTransactionModel{
public bool IsPending { get; set; }
public BankAccountTransactionModel2():
base(nameof(BankAccountTransactionModel2)) {}
}
In my controller I have something like this
[Route(".../api/[controller]")]
public class BankAccountTransactionsController : ApiBaseController
{
[HttpPost]
public IActionResult Post(BankAccountTransactionModel model) {
try {
if (model == null || !ModelState.IsValid) {
// failed to bind the model
return BadRequest(ModelState);
}
this.bankAccountTransactionRepository.SaveTransaction(model);
return this.CreatedAtRoute(ROUTE_NAME_GET_ITEM, new { id = model.Id }, model);
} catch (Exception e) {
this.logger.LogError(LoggingEvents.POST_ITEM, e, string.Empty, null);
return StatusCode(500);
}
}
}
My client may post either BankAccountTransactionModel1 or BankAccountTransactionModel2 and I would like to use a custom model binder to determine which concrete model to bind based on the value in the property ModelType which is defined on the abstract base class BankAccountTransactionModel.
Thus I have done the following
1) Coded up a simple Model Binder Provider that checks that the type is BankAccountTransactionModel. If this is the case then an instance of BankAccountTransactionModelBinder is returned.
public class BankAccountTransactionModelBinderProvider : IModelBinderProvider {
public IModelBinder GetBinder(ModelBinderProviderContext context) {
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) {
var type1 = context.Metadata.ModelType;
var type2 = typeof(BankAccountTransactionModel);
// some other code here?
// tried this but not sure what to do with it!
foreach (var property in context.Metadata.Properties) {
propertyBinders.Add(property, context.CreateBinder(property));
}
if (type1 == type2) {
return new BankAccountTransactionModelBinder(propertyBinders);
}
}
return null;
}
}
2) Coded up the BankAccountTransactionModel
public class BankAccountTransactionModelBinder : IModelBinder {
private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;
public BankAccountTransactionModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders){
this._propertyBinders = propertyBinders;
}
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
// I would like to be able to read the value of the property
// ModelType like this or in some way...
// This does not work and typeValue is...
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
// then once I know whether it is a Model1 or Model2 I would like to
// instantiate one and get the values from the body of the Http
// request into the properties of the instance
var model = Activator.CreateInstance(type);
// read the body of the request in some way and set the
// properties of model
var key = some key?
var result = ModelBindingResult.Success(key, model);
// Job done
return Task.FromResult(result);
}
}
3) Lastly I register the provider in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => {
options.ModelBinderProviders.Insert(0, new BankAccountTransactionModelBinderProvider());
options.Filters.Add(typeof (SetUserContextAttribute));
});
The whole thing seems OK in that the provider is actually invoked and the same is the case for the model builder. However, I cannot seem to get anywhere with coding the logic in BindModelAsync of the model binder.
As already stated by the comments in the code, all that I'd like to do in my model binder is to read from the body of the http request and in particular the value of ModelType in my JSON. Then on the bases of that I'd like to instantiate either BankAccountTransactionModel1 or BankAccountTransactionModel and finally assign values to the property of this instance by reading them of the JSON in the body.
I know that this is a only a gross approximation of how it should be done but I would greatly appreciate some help and perhaps example of how this could or has been done.
I have come across examples where the line of code below in the ModelBinder
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
is supposed to read the value. However, it does not work in my model binder and typeValue is always something like below
typeValue
{}
Culture: {}
FirstValue: null
Length: 0
Values: {}
Results View: Expanding the Results View will enumerate the IEnumerable
I have also noticed that
bindingContext.ValueProvider
Count = 2
[0]: {Microsoft.AspNetCore.Mvc.ModelBinding.RouteValueProvider}
[1]: {Microsoft.AspNetCore.Mvc.ModelBinding.QueryStringValueProvider}
Which probably means that as it is I do not stand a chance to read anything from the body.
Do I perhaps need a "formatter" in the mix in order to get desired result?
Does a reference implementation for a similar custom model binder already exist somewhere so that I can simply use it, perhaps with some simple mods?
Thank you.
I want to pass more than one object in the view. I have two object. One name is "Caller" and one Name is "Receiver". I am new in MVC. This is my action method.
public ActionResult IsActiveCaller(int id)
{
var caller = new CallerService().getCallerById(id);
if(caller.active)
{
var reciver= new reciverService().getReviverTime(caller.dialNo);
return (caller ) // here i also want to send reciver to view
}
return View();
}
Is there any way to send more than object in view?
Yes you can do this. There are multiple ways to do this.
1) You can use viewBag to pass the data or object into view.
You can see here to see how to use viewBag in mvc
2) you can use ViewData but it is not a good approach.
3) you can make ViewModel like as below (recomended)
public class callerReciver
{
public Caller caller {set;get;}
pblic Reciver eciver {set;get;}
}
Now pass callerReciver to view.You can access both object.hope you will understand.
4) Another way is to use partial view.You can make partial view to use more than one object in same view.
You can use a View Model:
public class MyViewModel
{
public Caller Caller { get; set; }
public Receiver Receiver { get; set; }
}
Then you can populate the view model this way:
public ActionResult IsActiveCaller(int id)
{
var caller = new CallerService().getCallerById(id);
var vm = new MyViewModel {
Caller = caller
};
vm.Receiver = caller.active ? new reciverService().getReviverTime(caller.dialNo) : null;
return View(vm);
}
View:
#model MyViewModel
<h1>#Model.Caller.Title</h1>
#if(Model.Receiver != null) {
<h1>#Model.Receiver.Title</h1>
}
The cleanest way is to pass by a view model :
ViewModel
public class MyViewModel {
public Caller MyCaller { get;set; }
public Receiver MyReceiver { get;set; }
}
Controller
public ActionResult IsActiveCaller(int id)
{
var caller = new CallerService().getCallerById(id);
var viewModel = new MyViewModel();
viewModel.MyCaller = caller;
if(caller.active)
{
var reciver= new reciverService().getReviverTime(caller.dialNo);
viewModel.MyReceiver = reciver;
}
return View(viewModel);
}
View
#model MyViewModel
<h1>#Model.MyCaller.Id</h1>
<h1>#Model.MyReceiver.Id</h1>
I can't find any reason for this unit test to fail, but it does every time.
HomeController.cs:
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
var model = "Whatever";
return View( model );
}
}
HomeControllerTest.cs:
[TestClass]
public class HomeControllerTest
{
[TestMethod]
public void Index()
{
var controller = new HomeController();
var result = controller.Index() as ViewResult;
var model = result.ViewData.Model;
Assert.IsInstanceOfType( model, typeof(string) );
}
}
result is not null, nor is result.ViewData. But result.ViewData.Model is always null.
What in the world is causing this to fail? It's such a simple test...
EDIT
This is even weirder. If I create a class, and use the class as the model, it doesn't fail. It only fails when the model is a string! i.e., this passes:
HomeController.cs:
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new SomeClass
{
Name = "Whatever"
};
return View( model );
}
}
public class SomeClass
{
public string Name { get; set; }
}
HomeControllerTest.cs
[TestClass]
public class HomeControllerTest
{
[TestMethod]
public void Index()
{
var controller = new HomeController();
var result = controller.Index() as ViewResult;
var model = result.ViewData.Model;
Assert.IsInstanceOfType( model, typeof(SomeClass) );
}
}
The model is null that's why.
When you pass a string as the argument to View(string), the string is actually the view name, not a model.
Cast it as an object to use the overload for the model.
return View((object)model);
View(Object) - Creates a ViewResult object by using the model that renders a view to the response.
View(String) - Creates a ViewResult object by using the view name that renders a view.
I am trying to move from webForms to Asp.net-MVC and have some problems. I am trying to figure why this is not working, I am getting this error: "Object reference not set to an instance of an object"
I have the class 'Pages':
namespace _2send.Model
{
public class Pages
{
public string PageContent { get; set; }
public string PageName { get; set; }
public int LanguageId { get; set; }
}
}
I am inserting the value to 'Pages.PageContent' property with this class:
namespace _2send.Model.Services
{
public class PagesService : IPagesService
{
public void GetFooterlinksPage()
{
DB_utilities db_util = new DB_utilities();
SqlDataReader dr;
Pages pages = new Pages();
using (dr = db_util.procSelect("[Pages_GetPageData]"))
{
if (dr.HasRows)
{
dr.Read();
pages.PageContent = (string)dr["PageContent"];
dr.Close();
}
}
}
The Controller method looks like this:
private IPagesService _pagesService;
public FooterLinksPageController(IPagesService pagesService)
{
_pagesService = pagesService;
}
public ActionResult GetFooterLinksPage()
{
_pagesService.GetFooterlinksPage();
return View();
}
I am trying to write the property in the view like this:
#model _2send.Model.Pages
<div>
#Model.PageContent;
</div>
When debugging, the method is fired and the dataReader is inserting the value to the 'PageContent' property, but I am still getting this error from the view.
Thanks!
return View();
You didn't pass a model.
You need to pass the model as a parameter to the View() method.
You need to rewrite service method to return Pages:
public Pages GetFooterlinksPage()
{
DB_utilities db_util = new DB_utilities();
Pages pages = new Pages();
using (var dr = db_util.procSelect("[Pages_GetPageData]"))
{
if (dr.HasRows)
{
dr.Read();
pages.PageContent = (string)dr["PageContent"];
return pages;
// Because you use using, you don't need to close datareader
}
}
}
And then rewrite your action method:
public ActionResult GetFooterLinksPage()
{
var viewmodel = _pagesService.GetFooterlinksPage();
return View(viewmodel);
}
You can return a model:
var viewmodel = new _2send.Model.Pages().
//here you configure your properties
return View(viewmodel);