I've implemented the Swagger Documentation in a web API made with C# (.NET Core 5).
I have 3 classes: DefaultCommandResult, SuccessCommandResult and ErrorCommandResult.
The DefaultCommandResult is the base class witch the other two inherit from. The object returned in the endpoint is either a SuccessCommandResult or ErrorCommandResult, and DefaultCommandResult is never returned.
What I need to do is hide the schema generated in the endpoints' responses related to the base class DefaultCommandResult:
The request in the controller:
public ActionResult<DefaultCommandResult<ServerIdCommand>> Get([FromQuery] RecoverServerIdCommand command)
{
try
{
var id = handler.RecoverServerId(command);
if (id is null)
return NotFound();
var output = new ServerIdCommand { Id = id };
var result = new SuccessCommandResult<ServerIdCommand>(output);
return Ok(result);
}
catch (ObjectNotFoundException ex)
{
var result = new ErrorCommandResult<string>(ex.Message);
return NotFound(result);
}
catch (Exception ex)
{
var result = new ErrorCommandResult<string>(ex.Message);
return BadRequest(result);
}
}
The DefaultCommandResult class:
[SwaggerSubType(typeof(SuccessCommandResult<object>))]
[SwaggerSubType(typeof(ErrorCommandResult<object>))]
public class DefaultCommandResult<T> where T : class
{
public bool Success { get; set; }
protected DefaultCommandResult(bool success)
{
Success = success;
}
}
The SuccessCommandResult class (ErrorCommandResult follows the same structure, but for errors):
[SwaggerDiscriminator("successCommandResult")]
public class SuccessCommandResult<T> : DefaultCommandResult<T> where T : class
{
public List<T> Data { get; private set; }
public SuccessCommandResult() : base(true) { }
public SuccessCommandResult(List<T> data) : base(true)
{
Data = data;
}
public SuccessCommandResult(T #object) : base(true)
{
if (Data is null)
Data = new List<T> { #object };
else
Data.Add(#object);
}
}
How can I achieve that using the Swashbuckle framework for C#? Thanks in advance.
The way I found a resolution for that was creating a Document Filter for the name of the schema:
public class SchemaNameFilter : IDocumentFilter
{
// w/o the filter: ServerIdCommandSuccessCommandResult. / with the filter: SuccessComandResult
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
foreach (var schema in swaggerDoc.Components.Schemas)
{
foreach (var schemaRepository in context.SchemaRepository.Schemas)
{
if (schema.Key.Equals(schemaRepository.Key))
{
switch (schema.Key)
{
case string x when !x.Equals(nameof(DefaultCommandResult)) && x.Contains(nameof(DefaultCommandResult)):
schema.Value.Title = nameof(DefaultCommandResult);
break;
case string x when !x.Equals(nameof(SuccessCommandResult<string>)) && x.Contains(nameof(SuccessCommandResult<string>)):
schema.Value.Title = nameof(SuccessCommandResult<string>);
break;
case string a when !a.Equals(nameof(OutputCommandResult)) && a.Contains(nameof(OutputCommandResult)):
schema.Value.Title = nameof(OutputCommandResult);
break;
}
}
}
}
}
}
What it does, basically, is rename the Swagger schema (with the incorrect name) to the name of the class.
Then, in the Startup class, add the document filter:
services.AddSwaggerGen(c =>
{
c.DocumentFilter<SchemaNameFilter>();
});
Related
I have 2 errors because of my little knowledge of PostSharp (last version). My project's three aspect classes, but I only get 2 errors. When the getall method runs in my mvc project, I want log information to be generated in my database and C:/Log/Log.txt. But no logs are created. Here is what I want from you. No matter how I write the code block, my problem solves? I have some validation and Transaction processes, but I don't think it has anything to do with the error I'm getting, so there's no need for details. Firstly, I've gotten the following warning.
enter image description here
To solve this, I followed the procedure below. LogAspect and FluentValidationAspect classes have been giving error.
[LogAspect(AspectPriority = 1), FluentValidationAspect(AspectPriority = 2), TransactionScopeAspect(AspectPriority = 3)]
And again, I got the following error. (CS 7036)
enter image description here
I want to do some operations in my BookManager class. (Log, Validation, Transaction). Here are the codes;
[LogAspect(AspectPriority = 1), FluentValidationAspect(AspectPriority = 2), TransactionScopeAspect(AspectPriority = 3)]
public class BookManager : IBookService
{
private IBookDal _bookDal;
private IPersonDal _personDal;
/* private readonly IQueryableRepository<Book> _queryable; */
public BookManager(IBookDal bookDal, IPersonDal personDal/*IQueryableRepository<Book> queryable */)
{
_personDal = personDal;
/*_queryable = queryable; */
_bookDal = bookDal;
}
[FluentValidationAspect(typeof(BookValidator))]
public Book Add(Book book)
{
return _bookDal.Add(book);
}
public void Delete(Book book)
{
_bookDal.Delete(book);
}
[LogAspect(typeof(DatabaseLogger))]
public List<Book> GetAll()
{
return _bookDal.GetList();
}
[TransactionScopeAspect]
public void TransactionalOperation(Person person, Book book)
{
_personDal.Delete(person);
// Business Codes
_bookDal.Add(book);
}
public Book GetById(int bookId)
{
return _bookDal.Get(p=>p.BookId==bookId);
}
[FluentValidationAspect(typeof(BookValidator))]
public Book Update(Book book)
{
return _bookDal.Update(book);
}
}
[Serializable]
[MulticastAttributeUsage(MulticastTargets.Method, TargetMemberAttributes = MulticastAttributes.Instance)]
public class LogAspect : OnMethodBoundaryAspect
{
private Type _loggerType;
[NonSerialized]
private LoggerService _loggerService;
public LogAspect(Type loggerType)
{
_loggerType = loggerType;
}
public override void RuntimeInitialize(MethodBase method)
{
if (_loggerType.BaseType != typeof(LoggerService))
{
throw new Exception("Wrong logger type.");
}
_loggerService = (LoggerService)Activator.CreateInstance(_loggerType);
base.RuntimeInitialize(method);
}
public override void OnEntry(MethodExecutionArgs args)
{
if (!_loggerService.IsInfoEnabled)
{
return;
}
try
{
var logParameters = args.Method.GetParameters().Select((t, i) => new LogParameter
{
Name = t.Name,
Type = t.ParameterType.Name,
Value = args.Arguments.GetArgument(i)
}).ToList();
var logDetail = new LogDetail
{
FullName = args.Method.DeclaringType == null ? null : args.Method.DeclaringType.Name,
MethodName = args.Method.Name,
Parameters = logParameters
};
_loggerService.Info(logDetail);
}
catch (Exception)
{
}
}
}
[Serializable]
public class FluentValidationAspect : OnMethodBoundaryAspect
{
Type _validatorType;
public FluentValidationAspect(Type validatorType)
{
_validatorType = validatorType;
}
public override void OnEntry(MethodExecutionArgs args)
{
var validator = (IValidator)Activator.CreateInstance(_validatorType);
var entityType = _validatorType.BaseType.GetGenericArguments()[0];
var entities = args.Arguments.Where(t => t.GetType() == entityType);
foreach (var entity in entities)
{
ValidatorTool.FluentValidate(validator, entity);
}
}
}
I want to tell you something that you have to consider. Also, I did assembly level logging. This is the code.
[assembly: LogAspect(typeof(JsonFileLogger), AttributeTargetTypes = "ELibrary.Library.Business.Managers.BookManager*")]
Finally I want to add, can you explain the solution of this error by rewriting the wrong block?
Ordering aspects is documented here. You are getting the warning because PostSharp does not have information on the ordering of some transformations. In your case I'd add the following attribute on LogAspect:
[AspectTypeDependency(AspectDependencyAction.Order, AspectDependencyPosition.Before, typeof(FluentValidationAspect))]
And then on FluentValidationAspect:
[AspectTypeDependency(AspectDependencyAction.Order, AspectDependencyPosition.Before, typeof(TransactionScopeAspect))]
This should totally order those three aspects and you should get rid of warnings.
Afterwards, you were getting C# errors because your aspect constructors simply have a parameter that you did not specify. AspectPriority needs to be specified on individual attributes.
I have been trying to figure out how to use custom model binding with .net Core 2 web api but have not been able to get it working.
I have been through some articles as below
http://www.palmmedia.de/Blog/2018/5/13/aspnet-core-model-binding-of-abstract-classes
Asp net core rc2. Abstract class model binding
In my case, the bindingContext.ModelName is always empty. Can anybody explain why this could be?
Sample implementation below
Controller
public IActionResult SomeAction([ModelBinder(BinderType = typeof(BlahTypeModelBinder))][FromBody]TheBaseClass theBase)
{
return Ok();
}
Models
public abstract class TheBaseClass
{
public abstract int WhatType { get; }
}
public class A : TheBaseClass
{
public override int WhatType { get { return 1; } }
}
public class B : TheBaseClass
{
public override int WhatType { get { return 2; } }
}
Provider
public class BhalTypeBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (context.Metadata.ModelType == typeof(TheBaseClass))
{
var assembly = typeof(TheBaseClass).Assembly;
var abstractSearchClasses = assembly.GetExportedTypes()
.Where(t => t.BaseType.Equals(typeof(TheBaseClass)))
.Where(t => !t.IsAbstract)
.ToList();
var modelBuilderByType = new Dictionary<Type, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder>();
foreach (var type in abstractSearchClasses)
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
var metadata = context.MetadataProvider.GetMetadataForType(type);
foreach (var property in metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
modelBuilderByType.Add(type, new Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder(propertyBinders));
}
return new BlahTypeModelBinder(modelBuilderByType, context.MetadataProvider);
}
return null;
}
}
Binder
public class BlahTypeModelBinder : IModelBinder
{
private readonly IModelMetadataProvider _metadataProvider;
private readonly IDictionary<Type, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder> _binders;
public BlahTypeModelBinder(IDictionary<Type, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder> binders, IModelMetadataProvider metadataProvider)
{
_metadataProvider = metadataProvider;
_binders = binders;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var modelTypeValue = bindingContext.ValueProvider.GetValue(ModelNames.CreatePropertyModelName(bindingContext.ModelName, "WhatType"));
if (modelTypeValue != null && modelTypeValue.FirstValue != null)
{
Type modelType = Type.GetType(modelTypeValue.FirstValue);
if (this._binders.TryGetValue(modelType, out var modelBinder))
{
ModelBindingContext innerModelBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
this._metadataProvider.GetMetadataForType(modelType),
null,
bindingContext.ModelName);
/*modelBinder*/
this._binders.First().Value.BindModelAsync(innerModelBindingContext);
bindingContext.Result = innerModelBindingContext.Result;
return Task.CompletedTask;
}
}
//More code
}
}
I finally managed to solve the issue. You dont need the provider. Just the following binder works
public class BlahTypeModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var json = ExtractRequestJson(bindingContext.ActionContext);
var jObject = Newtonsoft.Json.Linq.JObject.Parse(json);
var whatTypeInt = (int)jObject.SelectToken("WhatType");
if (whatTypeInt == 1)
{
var obj = DeserializeObject<A>(json);
bindingContext.Result = ModelBindingResult.Success(obj);
}
else if (whatTypeInt == 2)
{
var obj = DeserializeObject<B>(json);
bindingContext.Result = ModelBindingResult.Success(obj);
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
return Task.CompletedTask;
}
private static string ExtractRequestJson(ActionContext actionContext)
{
var content = actionContext.HttpContext.Request.Body;
return new StreamReader(content).ReadToEnd();
}
private static T DeserializeObject<T>(string json)
{
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(json, new Newtonsoft.Json.JsonSerializerSettings
{
TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto
});
}
}
The examples you linked to use an external query string parameter to determine the type.
If you call your action like this: SomeAction?WhatType=YourNamespaceName.A the binding works as expected.
bindingContext.ModelName being empty is just fine, it would be set after model binding. You can set it after setting bindingContext.Result if you want. Parameter WhatType comes from the QueryStringValueProvider, so no prefix is fine.
How to accomplish abstract model binding based on the JSON alone
To do this, we need:
A value provider to read the JSON and provide us with some "WhatType" value, in place of the QueryStringValueProvider.
Some reflection to map the extracted numbers to the Type-s.
1. ValueProvider
There is a detailed article on creating ValueProviders here:
As a starting point here is some code that successfully extracts the WhatType integers from the body json:
public class BlahValueProvider : IValueProvider
{
private readonly string _requestBody;
public BlahValueProvider(string requestBody)
{
_requestBody = requestBody;
}
private const string PROPERTY_NAME = "WhatType";
public bool ContainsPrefix(string prefix)
{
return prefix == PROPERTY_NAME;
}
public ValueProviderResult GetValue(string key)
{
if (key != PROPERTY_NAME)
return ValueProviderResult.None;
// parse json
try
{
var json = JObject.Parse(_requestBody);
return new ValueProviderResult(json.Value<int>("WhatType").ToString());
}
catch (Exception e)
{
// TODO: error handling
throw;
}
}
}
public class BlahValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var request = context.ActionContext.HttpContext.Request;
if (request.ContentType == "application/json")
{
return AddValueProviderAsync(context);
}
return Task.CompletedTask;
}
private Task AddValueProviderAsync(ValueProviderFactoryContext context)
{
using (StreamReader sr = new StreamReader(context.ActionContext.HttpContext.Request.Body))
{
string bodyString = sr.ReadToEnd();
context.ValueProviders.Add(new BlahValueProvider(bodyString));
}
return Task.CompletedTask;
}
}
Of course you have to register this factory in Startup.cs just as you registered the model binder. And this absolutely misses converting the extracted number to the actual Type (for this, see point 2 below), but if you place a breakpoint on your line staring with if (modelTypeValue != null you can see that modelTypeValue is there for you now even without the separate GET parameter.
2. Reflection
Realize that you are trying to figure out the type based on a property that is dynamically calculated on an existing instance (they are not static). While by knowing the current implementation I know that this is possible (create an empty instance of the model, check the WhatType property, throw the instance away), this is very bad practice, as nothing guarantees that an instance property is statically constant.
The clean solution for this would be an Attribute, that contains the WhatType number for that class. Then we can reflect on that attribute and build a map that maps ints to Types. This is out of the scope if this question, but look up any custom attribute tutorial if you are not familiar, and you will be able to put it together really quickly.
I'm new to Unity and am running into issues when it comes to classes that implement generic interfaces.
Suppose I have one controller and one service class, with the intention that they call the business layer based on an type identifier passed into the controller. Here's my layout:
Models:
public interface IModel
{
string Name { get; }
}
public class MyOrder : IModel
{
public string Name { get { return "Order"; } }
}
public class MyInvoice : IModel
{
public string Name { get { return "Invoice"; } }
}
Business Logic:
public interface ILogic
{
string GetModelName(IModel myModel);
}
public class MyOrderLogic : ILogic
{
public string GetModelName(IModel myModel)
{
return "MyOrderLogic : " + myModel.Name;
}
}
public class MyInvoiceLogic : ILogic
{
public string GetModelName(IModel myModel)
{
return "MyInvoiceLogic : " + myModel.Name;
}
}
public class LogicFactory
{
Func<IModel, ILogic> logicResolver;
public LogicFactory(Func<IModel, ILogic> resolver)
{
logicResolver = resolver;
}
public ILogic GetLogicForModel(IModel model)
{
return logicResolver(model);
}
}
Service:
public class MySingleService
{
private LogicFactory factory;
public MySingleService(LogicFactory f)
{
this.factory = f;
}
public IModel GetModel(int typeId, int objectId)
{
IModel model;
// This would really call Entity Framework context
switch (typeId)
{
case 1:
model = new MyOrder();
break;
default:
model = new MyInvoice();
break;
}
return model;
}
public void DoSomething(IModel model)
{
var logic = factory.GetLogicForModel(model);
var name = logic.GetModelName(model);
Console.WriteLine(name);
}
}
Controller:
public class MyController
{
MySingleService service;
public MyController(MySingleService s)
{
this.service = s;
}
public void DoAnAction(int typeId, int objectId)
{
var model = service.GetModel(typeId, objectId);
service.DoSomething(model);
}
}
This all works pretty well when I configure my Unity container like so
var container = new UnityContainer();
container.RegisterType<ILogic, MyOrderLogic>("MyOrder");
container.RegisterType<ILogic, MyInvoiceLogic>("MyInvoice");
Func<IModel, ILogic> resolver = (myModel) => container.Resolve<ILogic>(myModel.GetType().Name);
var logicFactory = new LogicFactory(resolver);
container.RegisterInstance<LogicFactory>(logicFactory);
var controller = container.Resolve<MyController>();
My issue is that I want to restrict the Business Logic classes so that they can only work on the proper model (i.e. I want MyOrderLogic to only accept MyOrder)
I wanted to have the business layer make use of generics, like so:
// Business Logic
public interface ILogic<T> where T : IModel
{
string GetModelName(T myModel);
}
public class MyOrderLogic : ILogic<MyOrder>
{
public string GetModelName(MyOrder myModel)
{
return "MyOrderLogic : " + myModel.Name;
}
}
public class MyInvoiceLogic : ILogic<MyInvoice>
{
public string GetModelName(MyInvoice myModel)
{
return "MyInvoiceLogic : " + myModel.Name;
}
}
This caused all kinds of problems between the Service and the Controller. I'm not sure how to properly resolve the *Logic classes based on a type identifier. I tried to do something similar to ILogic<IModel> logic = new MyOrderLogic(), but that obviously didn't work.
Is what I'm looking for possible? Is there something I can add to just the LogicFactory or something?
I want to avoid having individual controller and service objects as they would just have duplicate code.
I have created an interface that in theory should be able to return multiple generic lists of different types to provide the client with various information. When I attempt to loop through the results of the list it is only able to return first collection, can you help me to understand how I should be returning results from the following:
Interface class:
public interface IExampleInterface{}
public class ExampleType : IExampleInterface
{
public int First;
public int Last;
}
public class ExampleAmount : IExampleInterface
{
public decimal Amount;
public decimal TotalFee;
}
public class ExampleFacts : IExampleInterface
{
public bool TooLow;
public bool TooHigh;
}
Interface provider:
public class ExampleInterfaceProvider
{
private static readonly string conn = ConfigurationManager.ConnectionStrings["conn"].ConnectionString;
public static List<IExampleInterface> ExampleResults(int id)
{
//declare variables, read from database query using ExecuteReader...
var sT = new ExampleType
{
First = first;
Last = last;
}
var sA = new ExampleAmount
{
Amount = amount;
TotalFee = totalFee;
}
var sF = new ExampleFacts
{
TooHigh = tooHigh;
TooLow = tooLow;
}
var exampleResults = new List<IExampleInterface> {sT, sA, sF};
return exampleResults;
}
}
On the page I need to return the data:
foreach (dynamic item in ExampleResults(0))
{
Response.Write(item.First.ToString())
Response.Write(item.Last.ToString())
//The first two for 'sT' read fine, it breaks here
Response.Write(item.Amount.ToString())
//... And so on
}
Any help would be much appreciated,
Thanks
I think, there is no another solution except comparing implementations;
foreach (IExampleInterface item in ExampleResults(0))
{
if (item is ExampleType)
{
var exampleType = (ExampleType)item;
Response.Write(exampleType.First.ToString())
Response.Write(exampleType.Last.ToString())
}
else if (item is ExampleAmount)
{
var exampleAmount = (ExampleAmount)item;
Response.Write(exampleAmount.Amount.ToString())
}
//... And so on
}
If you are using C# 7, you can perform it as switch case
foreach (IExampleInterface item in ExampleResults(0))
{
switch (item)
{
case ExampleType c:
Response.Write(c.First.ToString());
Response.Write(c.Last.ToString());
break;
case ExampleAmount c:
Response.Write(c.Amount.ToString());
break;
default:
break;
}
//... And so on
}
You can find the documentation.
So basically, the items implementing IExampleInterface should all be written to a Response in a way that is somewhat specific to the actual type implementing the interface?
Then how about this:
public interface IExampleInterface
{
void WriteTo(Response response);
}
public class ExampleType : IExampleInterface
{
public int First;
public int Last;
public void WriteTo(Response response)
{
response.Write(First.ToString());
response.Write(Last.ToString());
}
}
public class ExampleAmount : IExampleInterface
{
public decimal Amount;
public decimal TotalFee;
public void WriteTo(Response response)
{
response.Write(Amount.ToString());
response.Write(TotalFee.ToString());
}
}
public class ExampleFacts : IExampleInterface
{
public bool TooLow;
public bool TooHigh;
public void WriteTo(Response response)
{
response.Write(TooLow.ToString());
response.Write(TooHigh.ToString());
}
}
And then:
foreach (IExampleInterface item in ExampleResults(0))
{
item.WriteTo(Response);
}
Assuming that Response is a variable holding an instance of the response rather than a static class.
This is a basically a class library project which is somehow exposed as a WCF service. The code below is a part of the Data Access Layer. 'db' is an object of a DataContext class. To save a file, we do the following-
public static Guid SaveFile(FileDetails fileDetails)
{
System.Nullable<Guid> id = null;
SystemDataContext.UsingWrite(db =>
{
db.SaveFileData(fileDetails.RunId, fileDetails.FileData, fileDetails.FileExtension, ref id);
});
return id ?? Guid.Empty;
}
Then, the below would execute-
public static void UsingWrite(Action<SoftCashCreditDBDataContext> action)
{
using (var context = new SystemDataContext())
{
try
{
action(context.Write);
}
catch (Exception ex)
{
DataAccessExceptionHandler.HandleExcetion(ex, Config.DataLayerPolicy);
}
}
}
public SystemDataContext()
{
if (_stack == null)
{
_stack = new Stack<SystemDataContext>();
this.Depth = 1;
this.Read = new SoftCashCreditDBDataContext(Config.ReadDatabaseConnection);
this.Write = new SoftCashCreditDBDataContext(Config.WriteDatabaseConnection);
}
else
{
var parent = _stack.Peek();
/// Increment level of node.
this.Depth = parent.Depth + 1;
/// Copy data context from the parent
this.Read = parent.Read;
this.Write = parent.Write;
}
_stack.Push(this);
}
public int Depth { get; private set; }
public bool IsRoot { get { return this.Depth == 1; } }
[ThreadStatic]
private static Stack<SystemDataContext> _stack = null;
public SoftCashCreditDBDataContext Read { get; private set; }
public SoftCashCreditDBDataContext Write { get; private set; }
#region IDisposable Members
public void Dispose()
{
var context = _stack.Pop();
if (context.IsRoot == true)
{
context.Read.Dispose();
context.Write.Dispose();
_stack = null;
}
}
#endregion
}
They have implemented LINQ to SQL here, and created a DBContext class. The 'SaveFileData()' method is actually part of that class, where it just calls an SP inside to save the file.
What I did not follow-
What exactly does the call to UsingWrite() do here? What is passed to the 'Action action' parameter, and what is it doing?
I understand your confusion. They use 2 delegates.
This is passed to the action parameter:
db =>
{
db.SaveFileData(fileDetails.RunId, fileDetails.FileData, fileDetails.FileExtension, ref id);
}
So when UsingWrite is called, the SoftCashCreditDBDataContext delegate which was set in the Write delegate will call SaveFileData.
A simplified example to help you understand Action:
public void Main()
{
Test(x => Debug.Write(x));
}
private void Test(Action<string> testAction)
{
testAction("Bla");
}
This function will call Debug.Write with the argument x, which is a string that is passed to the test action function.