mvc genericly binding byte[] properties - asp.net-mvc-5

I've tried using a custom model binder but my request.files is not populated. IN forms collection, the input of type file for the byte[] property is populated by file name only.
<input id="collection[#index].#p.Name" name="collection[#index].#p.Name" type="file" />
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace male.services.mvc.Binders
{
public class CustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType.GetProperties().Any(o => o.PropertyType == typeof(byte[])))
{
HttpRequestBase request = controllerContext.HttpContext.Request;
foreach (var pi in bindingContext.ModelType.GetProperties().Where(o => o.PropertyType == typeof(byte[])))
{
// can't access any property in the parameters that gives me my file input or my stream
}
return base.BindModel(controllerContext, bindingContext);
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
}
}

I found the answer. This will allow me to go straight from html file inputs to EF models without resorting to hand-crafting a file argument for each model type and property type which needs it. (Aint nobody got time for that)
In order for a form to post files, it must have the enctype attribute:
<form enctype="multipart/form-data" ... />
Once that is done, the file can be accessed via controllerContext.HttpContext.Request.Files
UPDATE - Working Example
Note: You don't need the Regex stuff if you aren't trying to bind a collection. You also don't need the EndsWith, you can just use ==
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
namespace male.services.mvc.Binders
{
public class CustomModelBinder : DefaultModelBinder
{
HttpRequestBase request;
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
request = controllerContext.HttpContext.Request;
return base.BindModel(controllerContext, bindingContext);
}
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(byte[]))
{
var key = request.Files.AllKeys.SingleOrDefault(o => o.EndsWith($"].{propertyDescriptor.Name}"));
var fileIndex = Regex.Match(key, #"\[(.*)\]").Groups[1].Value;
var modelIndex = Regex.Match(bindingContext.ModelName, #"\[(.*)\]").Groups[1].Value;
if(fileIndex == modelIndex)
{
HttpPostedFileWrapper httpPostedFile = (HttpPostedFileWrapper)request.Files[request.Files.AllKeys.SingleOrDefault(o => o.EndsWith(propertyDescriptor.Name))];
using (MemoryStream ms = new MemoryStream())
{
httpPostedFile.InputStream.CopyTo(ms);
propertyDescriptor.SetValue(bindingContext.Model, ms.ToArray());
}
}
}
else
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
}

Related

How can I invoke File Synchonization Action via code (SM202530)

We’re using file synchronization to send payment batch files to an external site. File Synchronization only works on a per file basis so we have set up one file that our export scenario will populate.
Because we need every version of this file to be sent to the external site, I need to make sure that every revision of the file is synchronized.
My thought was to override the Export button on Batch Payments (AP305000) to call the Process All Files action, using the Export File operation, on the File Synchronization screen. The challenge is that I can’t see to find the name of the graph to instantiate to access that business object.
Here is where I’m at:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using PX.Api;
using PX.Data;
using PX.Data.WorkflowAPI;
using PX.Objects.AP;
using PX.Objects.AR;
using PX.Objects.CM;
using PX.Objects.Common.Extensions;
using PX.Objects.CR;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.Objects.AP.MigrationMode;
using PX.Objects;
using PX.Objects.CA;
using PX.Objects.SM;
namespace PX.Objects.CA
{
public class CABatchEntry_Extension : PXGraphExtension<CABatchEntry>
{
[PXOverride]
public virtual IEnumerable Export(PXAdapter adapter)
{
Base.export.Press(adapter);
//Challenge is here
SynchronizationProcess docgraph = PXGraph.CreateInstance<SynchronizationProcess>();
foreach (var action in (docgraph.action.GetState(null) as PXButtonState).Menus)
{
if (action.Command == "Process All Files")
{
PXAdapter adapter2 = new PXAdapter(new DummyView(docgraph, docgraph.Document.View.BqlSelect, new List<object> { docgraph.Document.Current }));
adapter2.Menu = action.Command;
docgraph.action.PressButton(adapter2);
TimeSpan timespan;
Exception ex;
while (PXLongOperation.GetStatus(docgraph.UID, out timespan, out ex) == PXLongRunStatus.InProcess)
{ }
break;
}
}
return adapter.Get();
}
internal class DummyView : PXView
{
List<object> _Records;
internal DummyView(PXGraph graph, BqlCommand command, List<object> records)
: base(graph, true, command)
{
_Records = records;
}
public override List<object> Select(object[] currents, object[] parameters, object[] searches, string[] sortcolumns, bool[] descendings, PXFilterRow[] filters, ref int startRow, int maximumRows, ref int totalRows)
{
return _Records;
}
}
}
}
You can write your own PXLongOperation which will invoke the Processing Delegate the same way that page does, like below. Then you can use PXLongOperation.WaitCompletion(this.Base.UID) to wait till the process is completed and then do any other actions that you need to do.
public PXAction<SOOrder> RunSynchronization;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Sync Files")]
protected IEnumerable runSynchronization(PXAdapter adapter)
{
PXLongOperation.StartOperation(this.Base, () =>
{
SynchronizationProcess docgraph = PXGraph.CreateInstance<SynchronizationProcess>();
docgraph.filter.SetValueExt<SynchronizationFilter.operation>(docgraph.filter.Current,"U"); //U - export D - import
var records = docgraph.SelectedFiles.Select().RowCast<UploadFileWithIDSelector>().ToList();
docgraph.SelectedFiles.GetProcessDelegate().DynamicInvoke(records);
});
return adapter.Get();
}

How to render razor view as string in .net core web api?

I have .net core web api application. I need to send out the email using razor view. I cannot get razor view as string to send out the email.
I get the exception at Engine.Razor.RunCompile(razorView, "templateKey" + DateTime.Now.Ticks, null, model);
Below is my code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using MyProject.Api.Models;
using RazorEngine;
using RazorEngine.Templating;
namespace MyProject.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AppointmentBookingController : ControllerBase
{
private readonly IHostingEnvironment _hostingEnvironment;
public AppointmentBookingController(IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
[HttpGet]
[Route("GetViewAsString")]
public string GetViewAsString()
{
string webRootPath = _hostingEnvironment.WebRootPath;
string contentRootPath = _hostingEnvironment.ContentRootPath;
BaseHttpRequestModel model = new BaseHttpRequestModel
{
LanguageID = "ABC"
};
string razorView = System.IO.File.ReadAllText(contentRootPath + #"\Views\EmailTemplates\Test.cshtml");
Engine.Razor.RunCompile(razorView, "templateKey" + DateTime.Now.Ticks, null, model);
return razorView;
}
}
}
You need to supply the model type instead of passing null when using a strongly typed model. You pass null in when using anonymous or dynamic types. Also return the output of RunCompile:
...
var result = Engine.Razor.RunCompile(razorView, "templateKey" + DateTime.Now.Ticks, model.GetType(), model);
return result;

error : is a field but used as a type

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
namespace WindowsFormsApplication5
{
public class ClientContext
{
private string p;
public ClientContext(string p)
{
// TODO: Complete member initialization
this.p = p;
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//First construct client context, the object which will be responsible for
//communication with SharePoint:
private ClientContext context = new ClientContext("#url");
//then get a hold of the list item you want to download, for example
public List list;
public ClientContext
{
list = context.Web.Lists.GetByTitle("001_CFR_DPV_COST_REV_SHARING");
}
//note that data has not been loaded yet. In order to load the data
//you need to tell SharePoint client what you want to download:
context.Load(result, items=>items.Include(
item => item["Title"],
item => item["FileRef"]
));
//now you get the data
context.ExecuteQuery();
//here you have list items, but not their content (files). To download file
//you'll have to do something like this:
var item = items.First();
//get the URL of the file you want:
var fileRef = item["FileRef"];
//get the file contents:
FileInformation fileInfo = File.OpenBinaryDirect(context, fileRef.ToString());
using (var memory = new MemoryStream())
{
byte[] buffer = new byte[1024 * 64];
int nread = 0;
while ((nread = fileInfo.Stream.Read(buffer, 0, buffer.Length)) > 0)
{
memory.Write(buffer, 0, nread);
}
memory.Seek(0, SeekOrigin.Begin);
// ... here you have the contents of your file in memory,
// do whatever you want
}
}
}
this is the complete code.
I don't know why it is showing error. I searched for the error "is a field but used as a type" and I tried that but it didn't help. Please help with a solution Code to this since I am new to this. Thank you in advance.
What are you trying to achieve by this lines of code?
public partial class Form1 : Form
{
...
public ClientContext
{
list = context.Web.Lists.GetByTitle("001_CFR_DPV_COST_REV_SHARING");
}
}
What is public ClientContext {} inside class Form1 ?
It seems that you intended to create constructor to a class in another class and for compiler it looks more like a property but without accessors (get, set) as if it is a Type or smth like this.
Try to put get; set; accessors inside if you intended to create property:
public List Context
{
get
{
list = context.Web.Lists.GetByTitle("001_CFR_DPV_COST_REV_SHARING");
return list;
}
}
Or change it to method instead :
public void GetClientContext()
{
list = context.Web.Lists.GetByTitle("001_CFR_DPV_COST_REV_SHARING");
}

MVC 5 Custom Route - Error 404 in LegacyRoute : RouteBase (Example from the book Pro ASP.NET MVC 5 from Apress in chapter 16)

The code provided in the book Pro ASP.NET MVC 5 from Apress in chapter 16 (Routing Incoming URLs) The example is about legacy urls. Next i will put the code samples for the custom route, routeconfig, controller and view.
LegacyRoute.cs
using System;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace UrlsAndRoutes.Infrastructure
{
public class LegacyRoute : RouteBase
{
private string[] urls;
public LegacyRoute(params string[] targetUrls)
{
urls = targetUrls;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string requestedURL = httpContext.Request.AppRelativeCurrentExecutionFilePath;
if (urls.Contains(requestedURL, StringComparer.OrdinalIgnoreCase))
{
result = new RouteData(this, new MvcRouteHandler());
result.Values.Add("controller", "Legacy");
result.Values.Add("action", "GetLegacyURL");
result.Values.Add("legacyURL", requestedURL);
}
return result;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
VirtualPathData result = null;
if (values.ContainsKey("legacyURL") && urls.Contains((string)values["legacyURL"], StringComparer.OrdinalIgnoreCase))
{
result = new VirtualPathData(this, new UrlHelper(requestContext).Content((string)values["legacyURL"]).Substring(1));
}
return result;
}
}
}
RouteConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Mvc.Routing.Constraints;
using UrlsAndRoutes.Infrastructure;
namespace UrlsAndRoutes
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
//routes.MapRoute("NewRoute", "App/Do{action}", new { controller = "Home", id = UrlParameter.Optional });
//routes.Add(new Route("SayHello", new CustomRouteHandler()));
routes.Add(new LegacyRoute("~/articles/Windows_3.1_Overview.html", "~/old/.NET_1.0_Class_Library"));
routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home", action = "Index" });
routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" });
}
}
}
LegacyController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace UrlsAndRoutes.Controllers
{
public class LegacyController : Controller
{
public ActionResult GetLegacyURL(string legacyURL)
{
return View((object)legacyURL);
}
}
}
GetLegacyURL.cshtml
#model string
#{
ViewBag.Title = "GetLegacyURL";
Layout = null;
}
<h2>GetLegacyURL</h2>
The URL requested was: #Model
I can't figure how to put it work. I always get 404 error (http://my.machine/articles/Windows_3.1_Overview.html). The method in the GetRouteData in LegacyRoute.cs is never called. If I remove the . from the URL the code works fine. Can anyone give some advice or help?
It is strange that no one are complaining that the code doesn't work.
Page 445 provides the solution. You need to edit IIS Express settings.
Right click on the IIS Express icon on the taskbar while the application is running -> Show all applications -> Click on the site you want to configure -> Click on the configuration file
Search for System.Web.Routing.UrlRoutingModule
Remove the preCondition attribute value so the line becomes <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="" />
Restart the application and you are ready.
routes.MapMvcAttributeRoutes();
routes.Add(new LegacyRoute("~/articles/Windows_3.1_Overview.html/",
"~/old/.NET_1.0_Class_Library/"));
routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home" ,action="index" });
routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" });
Try with above code , this should be worked.
Malinda Sanjaka

Injecting dynamic content into an Orchard page body

Is it possible to inject dynamic content into an Orchard page body? I would like to inject the id of the current logged in user into links.
My initial thought was to use token replacement, so entering would replace [memberid] with the logged in user id at runtime. Is there a way I can intercept the response and run some custom code before it is sent back to the client?
This question is the same, however the answer is very specific to their issue.
Tokens might be the proper way to go, however I haven't digged into that yet. But if you want to change the response you can use response filters.
I used it to minify resulting HTML in this module https://gallery.orchardproject.net/List/Modules/Orchard.Module.JadeX.HtmlMarkupMinifier
Here's the code that should do the trick or at least give you an idea.
using System.Globalization;
using System.IO;
using System.Text;
using System.Web.Mvc;
using Orchard;
using Orchard.Mvc.Filters;
using Orchard.UI.Admin;
public class TokenReplacementFilter : FilterProvider, IActionFilter
{
private readonly WorkContext _workContext;
public TokenReplacementFilter(IWorkContextAccessor workContextAccessor)
{
_workContext = workContextAccessor.GetContext();
}
public void OnActionExecuting(ActionExecutingContext filterContext) {
// Only apply the token replacement if logged in and not in the Orchard admin area
if (filterContext.HttpContext.Response.Filter == null || _workContext.CurrentUser == null || AdminFilter.IsApplied(filterContext.RequestContext))
return;
filterContext.HttpContext.Response.Filter = new TokenReplacementStream(filterContext.HttpContext.Response.Filter, filterContext.HttpContext.Response.Output.Encoding, _workContext);
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
internal class TokenReplacementStream : MemoryStream
{
private readonly Stream _stream;
private readonly Encoding _encoding;
private string _html;
private readonly WorkContext _workContext;
public TokenReplacementStream(Stream filter, Encoding encoding, WorkContext workContext)
{
_stream = filter;
_encoding = encoding;
_workContext = workContext;
}
public override void Write(byte[] buffer, int offset, int count)
{
_html += _encoding.GetString(buffer);
}
public override void Flush()
{
if (_html != null) {
_html = _html.Replace("[memberid]", _workContext.CurrentUser.Id.ToString(CultureInfo.InvariantCulture));
_stream.Write(_encoding.GetBytes(_html), 0, _encoding.GetByteCount(_html));
}
}
}

Resources