ASP.Net MVC request ViewModel not parsing - c#

I have a request like this:
filter[logic]:and
filter[filters][0][value]:a
filter[filters][0][operator]:startswith
filter[filters][0][field]:result
filter[filters][0][ignoreCase]:true
I need to receive it on the Controller but I don't know exactly how. I have tried this view model:
{
public class SearchFilterViewModel
{
public string logic { get; set; }
public List<SearchFilterFiltersViewModel> filters { get; set; }
}
public class SearchFilterFiltersViewModel
{
public string value { get; set; }
//public string operator { get; set; }
public string field { get; set; }
public bool ignoreCase { get; set; }
}
}
But the Controller receives it all null. operator property is commented because operator is a reserved keyword, I don't know how to make Asp.Net to use it. And I don't know if this is the cause of the problem.
Note that I can't change the request body pattern because it comes from this Kendo Widget.
This is my Controller(test version):
public ActionResult Text(SearchFilterViewModel filter)
{
return Json("", JsonRequestBehavior.AllowGet);
}

Here is working solution
Model:
public class SearchFilterViewModel
{
public string logic { get; set; }
public List<SearchFilterFiltersViewModel> filter { get; set; }
}
public class SearchFilterFiltersViewModel
{
public string value { get; set; }
public string oper { get; set; }
public string field { get; set; }
public bool ignoreCase { get; set; }
}
Then you can write custom IValueProvider where you can override usual parsing mechanism like this:
public class KendoValueProvider : NameValueCollectionValueProvider
{
public KendoValueProvider(NameValueCollection originalCollection)
: base(UpdateCollection(originalCollection), CultureInfo.InvariantCulture)
{
}
private static NameValueCollection UpdateCollection(NameValueCollection collection)
{
NameValueCollection result = new NameValueCollection();
foreach (string key in collection.Keys)
{
// ignore all other request
if (!key.StartsWith("filter"))
return null;
var newKey = key
.Replace("[filters]", string.Empty)
.Replace("filter[logic]", "logic")
.Replace("[value]", ".value")
.Replace("[operator]", ".oper")
.Replace("[field]", ".field")
.Replace("[ignoreCase]", ".ignoreCase");
var value = collection[key];
result.Add(newKey, value);
}
return result;
}
}
Then you need to write ValueProviderFactory that will register this ValueProvider like this:
public class KendoValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
return new KendoValueProvider(controllerContext.HttpContext.Request.QueryString);
}
}
And the last step is just register it in Global.asax file
ValueProviderFactories.Factories.Add(new KendoValueProviderFactory());
And sample Action
[HttpGet]
public ActionResult Index(SearchFilterViewModel model)
{
return Json(model, JsonRequestBehavior.AllowGet);
}

Related

How to send data to view?

I have tables, to which I can send data, everything is set up, now I only need to understand, how to send data to view. When I want to get data using Model word, I'm getting error: NullReferenceException: Object reference not set to an instance of an object.
index.cshtml
#model FaqVM
#{
Layout = "_Layout";
}
#Model.MainCategoryTitle // Error
Faq.cs
public class Faq : CanBeLocalized, IHaveIntegerId, ICanProvideCreatedTime, IHaveConcurrencyToken
{
public int Id { get; set; }
[Localize] public string MainCategoryTitle { get; set; }
public DateTime Created { get; set; } = DateTime.Now;
public string ConcurrencyToken { get; set; }
public DateTime? Disabled { get; set; }
public int SOrder { get; set; } = 0;
public DateTime? Updated { get; set; }
public ICollection<FaqSubcategory> FaqSubcategories { get; set; } = new HashSet<FaqSubcategory>();
}
FaqVM.cs
public class FaqVM
{
public string MainCategoryTitle { get; set; }
public List<FaqSubcategory> FaqSubcategories { get; set; }
}
public class CmsController : Controller
{
protected ILocale _locale;
protected ICacheManager _cacheManager;
protected RegionProvider _regionProvider;
protected IViewRenderService _viewRenderService;
private IServiceProvider _serviceProvider;
//...
}
public class FaqController : CmsController
{
private readonly Faq _faq;
public FaqController(
RegionProvider regionProvider,
ILocaleAccessor localeAccessor,
ILiteralProvider literalProvider,
CartRepo cartRepo,
IServiceProvider serviceProvider,
Faq faq) : base(serviceProvider)
{
_faq = faq;
}
public async Task<IActionResult> Index()
{
var vm = new FaqVM
{
MainCategoryTitle = _faq.MainCategoryTitle,
FaqSubcategories = _faq.FaqSubcategories.ToList()
};
return View(vm);
}
}
Inside your Index() method in your FaqController you need to create your ViewModel as fx
var vm = new FaqVM(){
MainCategoryTitle = "test",
FaqSubcategories = new List<FaqSubcategory>(){ new FaqSubcategory() }
}
and then you need to pass the ViewModel to the return part of your Index method:
return View(vm);
Typically you would create your vm from some data from a database or some user input.

failing to bind string indexed query string parameters

I'm attempting to bind some query string parameters that is indexed by string keys but i can't seem to be getting it to work
here are the values i was trying to bind
search[value]: Exception happ...
search[regex]: false
here is the model i'm trying to bind it with
getLogsAjax(DataTableAjaxPostModel model)
public class DataTableAjaxPostModel
{
public int draw { get; set; }
public int start { get; set; }
public int length { get; set; }
public List<Column> columns { get; set; }
public search search { get; set; }
public List<Order> order { get; set; }
}
public class search
{
public string value { get; set; }
public string regex { get; set; }
}
the rest of the model is being bind correctly except for the search class object, i tripled check that the request contains values for that object, what am i missing here?
p.s. the same code was supposedly working pre .net core
A little more background of the code would be helpful such as the code section that is actually doing the binding however here is a dotnetcore controller example with query parameter binding. Also common practice in C# are class names and fields are both uppercase FYI.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
[Route("api/[controller]")]
public class SampleController : Controller
{
[HttpGet]
[Route("")]
public IActionResult ExampleGet([FromQuery] DataTableAjaxPostModel dataTableAjaxPostModel)
{
// You should be able to debug and see the value here
var result = dataTableAjaxPostModel.search;
return Ok();
}
public class DataTableAjaxPostModel
{
public int draw { get; set; }
public int start { get; set; }
public int length { get; set; }
public List<Column> columns { get; set; }
public search search { get; set; }
public List<Order> order { get; set; }
}
public class search
{
public string value { get; set; }
public string regex { get; set; }
}
}
You don't need to bind each field manually. Using reflection will make it easily.
Aslo, there's no need bind those outer model's properties (DataTableAjaxPostModel's properties) manually. That's because they will be done by the built-in model binder.
Implementation
create a custom binder QueryStringDictSyntaxBinder<TModel>:
internal class QueryStringDictSyntaxBinder<TModel> : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
try
{
var result = Activator.CreateInstance<TModel>();
foreach(var pi in typeof(TModel).GetProperties())
{
var modelName = bindingContext.ModelName;
var qsFieldName = $"{modelName}[{pi.Name}]";
var field= bindingContext.HttpContext.Request.Query[qsFieldName].FirstOrDefault();
if(field != null){
pi.SetValue(result,field);
}
// do nothing if null , or add model binding failure messages if you like
}
bindingContext.Result = ModelBindingResult.Success(result);
}
catch
{
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
And then decorate the search property with a [ModelBinder(typeof(QueryStringDictSyntaxBinder<search>))] :
public class DataTableAjaxPostModel
{
public int draw { get; set; }
public int start { get; set; }
public int length { get; set; }
public List columns { get; set; }
[ModelBinder(typeof(QueryStringDictSyntaxBinder<search>))]
public search search { get; set; }
public List order { get; set; }
}
Test Case:
I test it with the following requests, and it works fine for me:
?draw=1&search[value]=abc&search[regex]=(.*)&
?draw=1&sEarCh[value]=opq&Search[regex]=([^123]*)&
?draw=1&seaRch[value]=rst&Search[regex]=(.*)&
?draw=1&Search[value]=abc&
?draw=1&
seems like no one has an answer for this, so i took a different route and wrote my own custom binder, if a better answer came, ill accept it instead of this one, probably will refactor it later (hahaha IKR!)
public class DTModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
try
{
var result = new DataTableAjaxPostModel();
if (bindingContext.HttpContext.Request.Query.Keys.Contains("draw"))
result.draw = int.Parse(bindingContext.ValueProvider.GetValue("draw").FirstValue);
if (bindingContext.HttpContext.Request.Query.Keys.Contains("search[value]") &&
bindingContext.HttpContext.Request.Query.Keys.Contains("search[regex]"))
result.search = new search()
{
regex = bindingContext.ValueProvider.GetValue("search[regex]").FirstValue,
value = bindingContext.ValueProvider.GetValue("search[value]").FirstValue
};
//...
bindingContext.Result = ModelBindingResult.Success(result);
}
catch
{
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}

C#. Universal desition for storing different types

I'm looking for the best approach of working with different types identically.
I have a web service that goes to specific resource, makes some research and returns an object WebResult, that contains all information about completed operations.
And now I'd like to build a set of different metrics, that will describe all received results. These metrics should provide
different types of data
easy way to collect it
possibility to deserialize it.
Example 1
First I've created separate classes for different metrics
public abstract class AbstractStatistic
{
public string Url { get; set; }
public string ExceptionMessage { get; set; }
public abstract void FillAllMetrics(WebResult result);
}
public class Resource1Statistic : AbstractStatistic
{
public string Title { get; set; }
public string[] Table1_Header { get; set; }
public int Table1_RowCount { get; set; }
public string[] Table2_Header { get; set; }
public int Table2_RowCount { get; set; }
public override void FillAllMetrics(WebResult result)
{
this.Url = result.url;
this.Title = result.data["title"];
this.Table1_Header = result.data["table1.header"].ToObject<string[]>();
//...
}
}
It works, but I'd like to make it in more standard way. One of the reason is that in this approach I have to create separate web form for each metrics.
Example 2
Second working example is universal but redundant: create an abstraction of any datatype
public abstract class AbstractStatistic
{
public string Url { get; set; }
public string Exception { get; set; }
public Dictionary<string, Metric> Metrics { get ;set;}
public abstract void FillAllMetrics(WebResult webResult);
}
public class Metric // Specific class for data
{
public string StringValue { get; set; }
public int? IntegerValue { get; set; }
public string[] ArrayValue { get; set; }
public DateTime? DateTimeValue { get; set; }
}
public class Resource1Statistic : AbstractStatistic
{
public override void FillAllMetrics(WebResult result)
{
this.Metrics.Add("title",
new Metric() { StringValue = result.data["title"].ToString() });
this.Metrics.Add("Table1 Header",
new Metric() { ArrayValue = result.data["table1.header"].ToObject<string[]>() });
//...
}
It works, but I'm sure there is more elegant solution. I don't like to take all these null values in json.
Examples 3
Generic solution (regarding to Adwaenyth)
public abstract class AbstractStatistic
{
public string Url { get; set; }
public string Exception { get; set; }
public List<AbstractMetric> Metrics { get ;set;}
public abstract void FillAllMetrics(WebResult webResult);
}
public abstract class AbstractMetric{}
public class Metric<T> : AbstractMetric
{
public string Name { get; set; }
public T Value { get; set; }
public string Type { get; private set; }
public Metric()
{
this.Type = typeof(T).ToString();
}
}
public class Resource1Statistic : AbstractStatistic
{
public override void FillAllMetrics(WebResult result)
{
this.Metrics.Add(new Metric<string>()
{ Name = "title",
Value = result.data["title"].ToString() });
this.Metrics.Add(new Metric<string[]>()
{ Name = "Table1 Header",
Value = result.data["table1.header"].ToObject<string[]>() });
//...
}
This solution looks nice, but I have to write custom deserializer.
What do you think, is there some good pattern that fits to my task? Or what's the best approach?

ServiceStack Operation are not showing in metadata when using with Repository pattern

This is the DTO
public class Employee
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public string EmployeeeAddress { get; set; }
}
This is the Response
public class EmployeeResponse
{
public List<Employee> listofemp { get; set; }
}
This is the Service stack Service
public class EmployeeServices:Service
{
public dbRepo<Employee> objEmploye; //Repository (which is working fine)
public EmployeeServices()
{
objEmploye = new dbRepo<Employee>();
}
public object getAll(Employee obj)
{
var objlist = new EmployeeResponse {listofemp = objEmploye.GetAll().ToList()};
return objlist.listofemp;
}
}
this is the AppHostBase class
public class ServiceHostApp:AppHostBase
{
public ServiceHostApp()
: base("ServiceStack WebSerivces", typeof(EmployeeServices).Assembly)
{
}
public override void Configure(Funq.Container container)
{
}
}
My Question is why EmployeeServices Metod are not showing in the Metadata ?
is there any additional thing do i need to do ??
ServiceStack operates on the method names matching the Http Verb used, so instead of getAll you should really be using Get (or All if any verb can be used to do that).
No need to return an object...
public List<Employee> Get(Employee obj)
{
var objlist = new EmployeeResponse {listofemp = objEmploye.GetAll().ToList()};
return objlist.listofemp;
}
Lastly, you can always adorn Employee with:
[Route("/Employee")]
public class Employee
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public string EmployeeeAddress { get; set; }
}
That attribute is define in the namespace ServiceStack.ServiceHost.
You don't seem to have configured any routes and also haven't respected the naming convention of the service operation.
So you should decorate your request DTO with the Route attribute:
[Route("/employees")]
public class Employee
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public string EmployeeeAddress { get; set; }
}
and have your service operation be named with the HTTP verb you want it to be accessible with (GET in your case):
public object Get(Employee obj)
{
var objlist = new EmployeeResponse {listofemp = objEmploye.GetAll().ToList()};
return objlist.listofemp;
}
Now when you navigate to GET /employees the Get operation will be executed.
Bear in mind that you can also configure your routes using the Fluent API instead of using the Route attribute:
Routes.Add<Employee>("/employees", "GET");

Generic parameter problem

I have a business model which consists of a parent/child relationship (Identifier/IdentifierValue) and also some snapshot classes which look the same (IdentifierSnapshot/IdentifierValueSnapshot).
I am trying to create an extension method which will work on an enumeration of either Identifier or IdentifierSnapshot, but I just can't work out what to put in the extension method where I have inserted the XXX placeholder.
//Interfaces
public interface IIdentifier<TIdentifierValue>
where TIdentifierValue : IIdentifierValue
{
string Code { get; }
IEnumerable<TIdentifierValue> GetValues();
}
public interface IIdentifierValue
{
string Code { get; }
string Value { get; }
}
//Main classes
public class Identifier : IIdentifier<IdentifierValue>
{
public string Code { get; set; }
public IEnumerable<IdentifierValue> GetValues();
}
public class IdentifierValue : IIdentifierValue
{
public string Code { get; set; }
public string Value { get; set; }
}
//Snapshots
public class IdentifierSnapshot : IIdentifier<IdentifierValueSnapshot>
{
public string Code { get; set; }
public IEnumerable<IdentifierValueSnapshot> GetValues();
}
public class IdentifierValueSnapshot : IIdentifierValue
{
public string Code { get; set; }
public string Value { get; set; }
}
public static IdentifierExtensions
{
public static IEnumerable<XXX> ByCode<XXX>(this IEnumerable<XXX> instance, string code)
{
return instance.Where(x => string.Compare(code, x.Code, true) == 0);
}
}
I think this would do it:
public static IEnumerable<T> ByCode<T,Z>(this IEnumerable<T> instance, string code)
where T:IIdentifier<Z>
where Z:IIdentifierValue

Categories